aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/cron
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/cron')
-rw-r--r--usr.sbin/cron/Makefile3
-rw-r--r--usr.sbin/cron/Makefile.inc1
-rw-r--r--usr.sbin/cron/cron/Makefile13
-rw-r--r--usr.sbin/cron/cron/Makefile.depend18
-rw-r--r--usr.sbin/cron/cron/config.h87
-rw-r--r--usr.sbin/cron/cron/cron.8238
-rw-r--r--usr.sbin/cron/cron/cron.c568
-rw-r--r--usr.sbin/cron/cron/cron.h37
-rw-r--r--usr.sbin/cron/cron/crontab22
-rw-r--r--usr.sbin/cron/cron/database.c316
-rw-r--r--usr.sbin/cron/cron/do_command.c667
-rw-r--r--usr.sbin/cron/cron/externs.h128
-rw-r--r--usr.sbin/cron/cron/funcs.h70
-rw-r--r--usr.sbin/cron/cron/globals.h75
-rw-r--r--usr.sbin/cron/cron/job.c77
-rw-r--r--usr.sbin/cron/cron/macros.h134
-rw-r--r--usr.sbin/cron/cron/pathnames.h69
-rw-r--r--usr.sbin/cron/cron/popen.c238
-rw-r--r--usr.sbin/cron/cron/structs.h81
-rw-r--r--usr.sbin/cron/cron/user.c123
-rw-r--r--usr.sbin/cron/crontab/Makefile16
-rw-r--r--usr.sbin/cron/crontab/Makefile.depend18
-rw-r--r--usr.sbin/cron/crontab/crontab.1153
-rw-r--r--usr.sbin/cron/crontab/crontab.5393
-rw-r--r--usr.sbin/cron/crontab/crontab.c632
-rw-r--r--usr.sbin/cron/doc/CHANGES157
-rw-r--r--usr.sbin/cron/doc/CONVERSION84
-rw-r--r--usr.sbin/cron/doc/FEATURES83
-rw-r--r--usr.sbin/cron/doc/INSTALL91
-rw-r--r--usr.sbin/cron/doc/MAIL473
-rw-r--r--usr.sbin/cron/doc/Makefile.vixie124
-rw-r--r--usr.sbin/cron/doc/README68
-rw-r--r--usr.sbin/cron/doc/README.1ST4
-rw-r--r--usr.sbin/cron/doc/THANKS29
-rw-r--r--usr.sbin/cron/lib/Makefile10
-rw-r--r--usr.sbin/cron/lib/Makefile.depend13
-rw-r--r--usr.sbin/cron/lib/entry.c729
-rw-r--r--usr.sbin/cron/lib/env.c260
-rw-r--r--usr.sbin/cron/lib/misc.c584
39 files changed, 6886 insertions, 0 deletions
diff --git a/usr.sbin/cron/Makefile b/usr.sbin/cron/Makefile
new file mode 100644
index 000000000000..1131cff7b17f
--- /dev/null
+++ b/usr.sbin/cron/Makefile
@@ -0,0 +1,3 @@
+SUBDIR= lib cron crontab
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/cron/Makefile.inc b/usr.sbin/cron/Makefile.inc
new file mode 100644
index 000000000000..01b5f23410c8
--- /dev/null
+++ b/usr.sbin/cron/Makefile.inc
@@ -0,0 +1 @@
+.include "../Makefile.inc"
diff --git a/usr.sbin/cron/cron/Makefile b/usr.sbin/cron/cron/Makefile
new file mode 100644
index 000000000000..c86f4fc56b71
--- /dev/null
+++ b/usr.sbin/cron/cron/Makefile
@@ -0,0 +1,13 @@
+PACKAGE=cron
+CONFS= crontab
+PROG= cron
+MAN= cron.8
+SRCS= cron.c database.c do_command.c job.c user.c popen.c
+
+CFLAGS+= -DLOGIN_CAP -DPAM
+
+LIBADD= cron pam util
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/cron/cron/Makefile.depend b/usr.sbin/cron/cron/Makefile.depend
new file mode 100644
index 000000000000..474d85363f7c
--- /dev/null
+++ b/usr.sbin/cron/cron/Makefile.depend
@@ -0,0 +1,18 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libpam/libpam \
+ lib/libutil \
+ usr.sbin/cron/lib \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/cron/cron/config.h b/usr.sbin/cron/cron/config.h
new file mode 100644
index 000000000000..ea30c8def194
--- /dev/null
+++ b/usr.sbin/cron/cron/config.h
@@ -0,0 +1,87 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/* config.h - configurables for Vixie Cron
+ *
+ * $Id: config.h,v 1.2 1998/08/14 00:32:36 vixie Exp $
+ */
+
+/*
+ * these are site-dependent
+ */
+
+#ifndef DEBUGGING
+#define DEBUGGING 1 /* 1 or 0 -- do you want debugging code built in? */
+#endif
+
+ /*
+ * choose one of these mailer commands. some use
+ * /bin/mail for speed; it makes biff bark but doesn't
+ * do aliasing. sendmail does do 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 MAILFMT "%s -FCronDaemon -odi -oem -oi -t" /*-*/
+ /* -Fx = set full-name of sender
+ * -odi = Option Deliverymode Interactive
+ * -oem = Option Errors Mailedtosender
+ * -or0s = Option Readtimeout -- don't time out
+ * -t = Get recipient from headers
+ */
+#define MAILARG _PATH_SENDMAIL /*-*/
+
+/* #define MAILFMT "%s -d %s" */ /*-*/
+ /* -d = undocumented but common flag: deliver locally?
+ */
+/* #define MAILARG "/bin/mail",mailto */
+
+/* #define MAILFMT "%s -mlrxto %s" */ /*-*/
+/* #define MAILARG "/usr/mmdf/bin/submit",mailto */ /*-*/
+
+/* #define MAIL_DATE */ /*-*/
+ /* should we include an ersatz Date: header in
+ * generated mail? if you are using sendmail
+ * as the mailer, 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/cron.8 b/usr.sbin/cron/cron/cron.8
new file mode 100644
index 000000000000..782bbce2fb6e
--- /dev/null
+++ b/usr.sbin/cron/cron/cron.8
@@ -0,0 +1,238 @@
+.\"/* Copyright 1988,1990,1993,1996 by Paul Vixie
+.\" * All rights reserved
+.\" */
+.\"
+.\"Copyright (c) 1997 by Internet Software Consortium
+.\"
+.\"Permission to use, copy, modify, and distribute this software for any
+.\"purpose with or without fee is hereby granted, provided that the above
+.\"copyright notice and this permission notice appear in all copies.
+.\"
+.\"THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+.\"ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+.\"OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+.\"CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+.\"DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+.\"PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+.\"ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+.\"SOFTWARE.
+.\"
+.\" $Id: cron.8,v 1.2 1998/08/14 00:32:36 vixie Exp $
+.\"
+.Dd February 9, 2022
+.Dt CRON 8
+.Os
+.Sh NAME
+.Nm cron
+.Nd daemon to execute scheduled commands (Vixie Cron)
+.Sh SYNOPSIS
+.Nm
+.Op Fl j Ar jitter
+.Op Fl J Ar rootjitter
+.Op Fl m Ar mailto
+.Op Fl n
+.Op Fl s
+.Op Fl o
+.Op Fl x Ar debugflag Ns Op , Ns Ar ...
+.Sh DESCRIPTION
+The
+.Nm
+utility should be started from
+.Pa /etc/rc
+or
+.Pa /etc/rc.local .
+It will return immediately,
+so you do not need to start it with '&'.
+.Pp
+The
+.Nm
+utility searches
+.Pa /var/cron/tabs
+for crontab files which are named after accounts in
+.Pa /etc/passwd ;
+crontabs found are loaded into memory.
+The
+.Nm
+utility also searches for
+.Pa /etc/crontab
+and files in
+.Pa /etc/cron.d
+and
+.Pa /usr/local/etc/cron.d
+which are in a different format (see
+.Xr crontab 5 ) .
+.Pp
+The
+.Nm
+utility
+then wakes up every minute, examining all stored crontabs, checking each
+command to see if it should be run in the current minute.
+Before running a command from a per-account crontab file,
+.Nm
+checks the status of the account with
+.Xr pam 3
+and skips the command if the account is unavailable,
+e.g., locked out or expired.
+Commands from
+.Pa /etc/crontab
+bypass this check.
+When executing
+commands, any output is mailed to the owner of the crontab (or to the user
+named in the
+.Ev MAILTO
+environment variable in the crontab, if such exists).
+The from address of this mail may be set with the
+.Ev MAILFROM
+environment variable.
+.Pp
+Additionally,
+.Nm
+checks each minute to see if its spool directory's modification time (or
+the modification time on
+.Pa /etc/crontab )
+has changed, and if it has,
+.Nm
+will then examine the modification time on all crontabs and reload those
+which have changed.
+Thus
+.Nm
+need not be restarted whenever a crontab file is modified.
+Note that the
+.Xr crontab 1
+command updates the modification time of the spool directory whenever it
+changes a crontab.
+.Pp
+Available options:
+.Bl -tag -width indent
+.It Fl j Ar jitter
+Enable time jitter.
+Prior to executing commands,
+.Nm
+will sleep a random number of seconds in the range from 0 to
+.Ar jitter .
+This will not affect superuser jobs (see
+.Fl J ) .
+A value for
+.Ar jitter
+must be between 0 and 60 inclusive.
+Default is 0, which effectively disables time jitter.
+.Pp
+This option can help to smooth down system load spikes during
+moments when a lot of jobs are likely to start at once, e.g.,
+at the beginning of the first minute of each hour.
+.It Fl J Ar rootjitter
+Enable time jitter for superuser jobs.
+The same as
+.Fl j
+except that it will affect jobs run by the superuser only.
+.It Fl m Ar mailto
+Overrides the default recipient for
+.Nm
+mail.
+Each
+.Xr crontab 5
+without
+.Ev MAILTO
+explicitly set will send mail to the
+.Ar mailto
+mailbox.
+Sending mail will be disabled by default if
+.Ar mailto
+set to a null string, usually specified in a shell as
+.Li ''
+or
+.Li \*q\*q .
+.It Fl n
+Do not daemonize; run in foreground instead.
+.It Fl s
+Enable special handling of situations when the GMT offset of the local
+timezone changes, such as the switches between the standard time and
+daylight saving time.
+.Pp
+The jobs run during the GMT offset changes time as
+intuitively expected.
+If a job falls into a time interval that disappears
+(for example, during the switch from
+standard time) to daylight saving time or is
+duplicated (for example, during the reverse switch), then it is handled
+in one of two ways:
+.Pp
+The first case is for the jobs that run every at hour of a time interval
+overlapping with the disappearing or duplicated interval.
+In other words, if the job had run within one hour before the GMT offset change
+(and cron was not restarted nor the
+.Xr crontab 5
+changed after that)
+or would run after the change at the next hour.
+They work as always, skip the skipped time or run in the added
+time as usual.
+.Pp
+The second case is for the jobs that run less frequently.
+They are executed exactly once, they are not skipped nor
+executed twice (unless cron is restarted or the user's
+.Xr crontab 5
+is changed during such a time interval).
+If an interval disappears
+due to the GMT offset change, such jobs are
+executed at the same absolute point of time as they would be in the
+old time zone.
+For example, if exactly one hour disappears, this
+point would be during the next hour at the first minute that is
+specified for them in
+.Xr crontab 5 .
+.It Fl o
+Disable the special handling of situations when the GMT offset of the local
+timezone changes, to be compatible with the old (default) behavior.
+If both options
+.Fl o
+and
+.Fl s
+are specified, the option specified last wins.
+.It Fl x Ar debugflag Ns Op , Ns Ar ...
+Enable writing of debugging information to standard output.
+One or more of the following comma separated
+.Ar debugflag
+identifiers must be specified:
+.Pp
+.Bl -tag -width ".Cm proc" -compact
+.It Cm bit
+currently not used
+.It Cm ext
+make the other debug flags more verbose
+.It Cm load
+be verbose when loading crontab files
+.It Cm misc
+be verbose about miscellaneous one-off events
+.It Cm pars
+be verbose about parsing individual crontab lines
+.It Cm proc
+be verbose about the state of the process, including all of its offspring
+.It Cm sch
+be verbose when iterating through the scheduling algorithms
+.It Cm test
+trace through the execution, but do not perform any actions
+.El
+.El
+.Sh FILES
+.Bl -tag -width /usr/local/etc/cron.d -compact
+.It Pa /etc/crontab
+System crontab file
+.It Pa /etc/cron.d
+Directory for optional/modularized system crontab files.
+.It Pa /etc/pam.d/cron
+.Xr pam.conf 5
+configuration file for
+.Nm
+.It Pa /usr/local/etc/cron.d
+Directory for third-party package provided crontab files.
+.It Pa /var/cron/tabs
+Directory for personal crontab files
+.El
+.Sh SEE ALSO
+.Xr crontab 1 ,
+.Xr pam 3 ,
+.Xr crontab 5 ,
+.Xr pam.conf 5 ,
+.Xr periodic 8
+.Sh AUTHORS
+.An Paul Vixie Aq Mt paul@vix.com
diff --git a/usr.sbin/cron/cron/cron.c b/usr.sbin/cron/cron/cron.c
new file mode 100644
index 000000000000..46ed984711a5
--- /dev/null
+++ b/usr.sbin/cron/cron/cron.c
@@ -0,0 +1,568 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$Id: cron.c,v 1.3 1998/08/14 00:32:36 vixie Exp $";
+#endif
+
+#define MAIN_PROGRAM
+
+#include "cron.h"
+#include <sys/mman.h>
+
+static void usage(void),
+ run_reboot_jobs(cron_db *),
+ cron_tick(cron_db *, int),
+ cron_sync(int),
+ cron_sleep(cron_db *, int),
+ cron_clean(cron_db *),
+ sigchld_handler(int),
+ sighup_handler(int),
+ parse_args(int c, char *v[]);
+
+static int run_at_secres(cron_db *);
+static void find_interval_entry(pid_t);
+
+static cron_db database;
+static time_t last_time = 0;
+static int dst_enabled = 0;
+static int dont_daemonize = 0;
+struct pidfh *pfh;
+
+static void
+usage(void)
+{
+#if DEBUGGING
+ const char **dflags;
+#endif
+
+ fprintf(stderr, "usage: cron [-j jitter] [-J rootjitter] "
+ "[-m mailto] [-n] [-s] [-o] [-x debugflag[,...]]\n");
+#if DEBUGGING
+ fprintf(stderr, "\ndebugflags: ");
+
+ for (dflags = DebugFlagNames; *dflags; dflags++) {
+ fprintf(stderr, "%s ", *dflags);
+ }
+ fprintf(stderr, "\n");
+#endif
+
+ exit(ERROR_EXIT);
+}
+
+static void
+open_pidfile(void)
+{
+ const char *pidfile = PIDDIR PIDFILE;
+ char buf[MAX_TEMPSTR];
+ int otherpid;
+
+ pfh = pidfile_open(pidfile, 0600, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST) {
+ snprintf(buf, sizeof(buf),
+ "cron already running, pid: %d", otherpid);
+ } else {
+ snprintf(buf, sizeof(buf),
+ "can't open or create %s: %s", pidfile,
+ strerror(errno));
+ }
+ log_it("CRON", getpid(), "DEATH", buf);
+ errx(ERROR_EXIT, "%s", buf);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int runnum;
+ int secres1, secres2;
+ struct tm *tm;
+
+ ProgramName = argv[0];
+
+#if defined(BSD)
+ setlinebuf(stdout);
+ setlinebuf(stderr);
+#endif
+
+ parse_args(argc, argv);
+
+ (void) signal(SIGCHLD, sigchld_handler);
+ (void) signal(SIGHUP, sighup_handler);
+
+ open_pidfile();
+ set_cron_uid();
+ set_cron_cwd();
+
+ putenv("PATH="_PATH_DEFPATH);
+
+ /* 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 if (dont_daemonize == 0) {
+ if (daemon(1, 0) == -1) {
+ pidfile_remove(pfh);
+ log_it("CRON",getpid(),"DEATH","can't become daemon");
+ exit(0);
+ }
+ }
+
+ if (madvise(NULL, 0, MADV_PROTECT) != 0)
+ log_it("CRON", getpid(), "WARNING", "madvise() failed");
+
+ pidfile_write(pfh);
+ database.head = NULL;
+ database.tail = NULL;
+ database.mtime = (time_t) 0;
+ load_database(&database);
+ secres1 = secres2 = run_at_secres(&database);
+ cron_sync(secres1);
+ run_reboot_jobs(&database);
+ runnum = 0;
+ while (TRUE) {
+# if DEBUGGING
+ /* if (!(DebugFlags & DTEST)) */
+# endif /*DEBUGGING*/
+ cron_sleep(&database, secres1);
+
+ if (secres1 == 0 || runnum % 60 == 0) {
+ load_database(&database);
+ secres2 = run_at_secres(&database);
+ if (secres2 != secres1) {
+ secres1 = secres2;
+ if (secres1 != 0) {
+ runnum = 0;
+ } else {
+ /*
+ * Going from 1 sec to 60 sec res. If we
+ * are already at minute's boundary, so
+ * let it run, otherwise schedule for the
+ * next minute.
+ */
+ tm = localtime(&TargetTime);
+ if (tm->tm_sec > 0) {
+ cron_sync(secres2);
+ continue;
+ }
+ }
+ }
+ }
+
+ /* do this iteration
+ */
+ cron_tick(&database, secres1);
+
+ /* sleep 1 or 60 seconds
+ */
+ TargetTime += (secres1 != 0) ? 1 : 60;
+ runnum += 1;
+ }
+}
+
+static void
+run_reboot_jobs(cron_db *db)
+{
+ user *u;
+ 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);
+ }
+ if (e->flags & INTERVAL) {
+ e->lastexit = TargetTime;
+ }
+ }
+ }
+ (void) job_runqueue();
+}
+
+static void
+cron_tick(cron_db *db, int secres)
+{
+ static struct tm lasttm;
+ /* time difference in seconds from the last offset change */
+ static time_t diff = 0;
+ /* end point for the time zone correction */
+ static time_t difflimit = 0;
+ /* time in the old time zone */
+ struct tm otztm;
+ int otzsecond, otzminute, otzhour, otzdom, otzmonth, otzdow;
+ struct tm *tm = localtime(&TargetTime);
+ int second, minute, hour, dom, month, dow;
+ user *u;
+ entry *e;
+
+ /* make 0-based values out of these so we can use them as indices
+ */
+ second = (secres == 0) ? 0 : tm->tm_sec -FIRST_SECOND;
+ 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,%d)\n",
+ getpid(), second, minute, hour, dom, month, dow))
+
+ if (dst_enabled && last_time != 0
+ && TargetTime > last_time /* exclude stepping back */
+ && tm->tm_gmtoff != lasttm.tm_gmtoff ) {
+
+ diff = tm->tm_gmtoff - lasttm.tm_gmtoff;
+
+ if ( diff > 0 ) { /* ST->DST */
+ /* mark jobs for an earlier run */
+ difflimit = TargetTime + diff;
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ e->flags &= ~NOT_UNTIL;
+ if ( e->lastrun >= TargetTime )
+ e->lastrun = 0;
+ /* not include the ends of hourly ranges */
+ if ( e->lastrun < TargetTime - 3600 )
+ e->flags |= RUN_AT;
+ else
+ e->flags &= ~RUN_AT;
+ }
+ }
+ } else { /* diff < 0 : DST->ST */
+ /* mark jobs for skipping */
+ difflimit = TargetTime - diff;
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ e->flags |= NOT_UNTIL;
+ e->flags &= ~RUN_AT;
+ }
+ }
+ }
+ }
+
+ if (diff != 0) {
+ /* if the time was reset of the end of special zone is reached */
+ if (last_time == 0 || TargetTime >= difflimit) {
+ /* disable the TZ switch checks */
+ diff = 0;
+ difflimit = 0;
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ e->flags &= ~(RUN_AT|NOT_UNTIL);
+ }
+ }
+ } else {
+ /* get the time in the old time zone */
+ time_t difftime = TargetTime + tm->tm_gmtoff - diff;
+ gmtime_r(&difftime, &otztm);
+
+ /* make 0-based values out of these so we can use them as indices
+ */
+ otzsecond = (secres == 0) ? 0 : otztm.tm_sec -FIRST_SECOND;
+ otzminute = otztm.tm_min -FIRST_MINUTE;
+ otzhour = otztm.tm_hour -FIRST_HOUR;
+ otzdom = otztm.tm_mday -FIRST_DOM;
+ otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
+ otzdow = otztm.tm_wday -FIRST_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 (e->flags & INTERVAL) {
+ if (e->lastexit > 0 &&
+ TargetTime >= e->lastexit + e->interval)
+ job_add(e, u);
+ continue;
+ }
+
+ if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) {
+ if (bit_test(e->second, otzsecond) &&
+ bit_test(e->minute, otzminute) &&
+ bit_test(e->hour, otzhour) &&
+ bit_test(e->month, otzmonth) &&
+ ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
+ ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom))
+ : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom))
+ )
+ ) {
+ if ( e->flags & RUN_AT ) {
+ e->flags &= ~RUN_AT;
+ e->lastrun = TargetTime;
+ job_add(e, u);
+ continue;
+ } else
+ e->flags &= ~NOT_UNTIL;
+ } else if ( e->flags & NOT_UNTIL )
+ continue;
+ }
+
+ if (bit_test(e->second, second) &&
+ 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))
+ )
+ ) {
+ e->flags &= ~RUN_AT;
+ e->lastrun = TargetTime;
+ job_add(e, u);
+ }
+ }
+ }
+
+ last_time = TargetTime;
+ lasttm = *tm;
+}
+
+/* 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(int secres) {
+ struct tm *tm;
+
+ TargetTime = time((time_t*)0);
+ if (secres != 0) {
+ TargetTime += 1;
+ } else {
+ tm = localtime(&TargetTime);
+ TargetTime += (60 - tm->tm_sec);
+ }
+}
+
+static void
+timespec_subtract(struct timespec *result, struct timespec *x,
+ struct timespec *y)
+{
+ *result = *x;
+ result->tv_sec -= y->tv_sec;
+ result->tv_nsec -= y->tv_nsec;
+ if (result->tv_nsec < 0) {
+ result->tv_sec--;
+ result->tv_nsec += 1000000000;
+ }
+}
+
+static void
+cron_sleep(cron_db *db, int secres)
+{
+ int seconds_to_wait;
+ int rval;
+ struct timespec ctime, ttime, stime, remtime;
+
+ /*
+ * Loop until we reach the top of the next minute, sleep when possible.
+ */
+
+ for (;;) {
+ clock_gettime(CLOCK_REALTIME, &ctime);
+ ttime.tv_sec = TargetTime;
+ ttime.tv_nsec = 0;
+ timespec_subtract(&stime, &ttime, &ctime);
+
+ /*
+ * If the seconds_to_wait value is insane, jump the cron
+ */
+
+ if (stime.tv_sec < -600 || stime.tv_sec > 600) {
+ cron_clean(db);
+ cron_sync(secres);
+ continue;
+ }
+
+ seconds_to_wait = (stime.tv_nsec > 0) ? stime.tv_sec + 1 :
+ stime.tv_sec;
+
+ Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
+ getpid(), (long)TargetTime, seconds_to_wait))
+
+ /*
+ * If we've run out of wait time or there are no jobs left
+ * to run, break
+ */
+
+ if (stime.tv_sec < 0)
+ break;
+ if (job_runqueue() == 0) {
+ Debug(DSCH, ("[%d] sleeping for %d seconds\n",
+ getpid(), seconds_to_wait))
+
+ for (;;) {
+ rval = nanosleep(&stime, &remtime);
+ if (rval == 0 || errno != EINTR)
+ break;
+ stime.tv_sec = remtime.tv_sec;
+ stime.tv_nsec = remtime.tv_nsec;
+ }
+ }
+ }
+}
+
+
+/* if the time was changed abruptly, clear the flags related
+ * to the daylight time switch handling to avoid strange effects
+ */
+
+static void
+cron_clean(cron_db *db)
+{
+ user *u;
+ entry *e;
+
+ last_time = 0;
+
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ e->flags &= ~(RUN_AT|NOT_UNTIL);
+ }
+ }
+}
+
+static void
+sigchld_handler(int x)
+{
+ WAIT_T waiter;
+ PID_T pid;
+
+ for (;;) {
+ pid = waitpid(-1, &waiter, WNOHANG);
+ 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:
+ find_interval_entry(pid);
+ Debug(DPROC,
+ ("[%d] sigchld...pid #%d died, stat=%d\n",
+ getpid(), pid, WEXITSTATUS(waiter)))
+ }
+ }
+}
+
+static void
+sighup_handler(int x)
+{
+ log_close();
+}
+
+static void
+parse_args(int argc, char *argv[])
+{
+ int argch;
+ char *endp;
+
+ while ((argch = getopt(argc, argv, "j:J:m:nosx:")) != -1) {
+ switch (argch) {
+ case 'j':
+ Jitter = strtoul(optarg, &endp, 10);
+ if (*optarg == '\0' || *endp != '\0' || Jitter > 60)
+ errx(ERROR_EXIT,
+ "bad value for jitter: %s", optarg);
+ break;
+ case 'J':
+ RootJitter = strtoul(optarg, &endp, 10);
+ if (*optarg == '\0' || *endp != '\0' || RootJitter > 60)
+ errx(ERROR_EXIT,
+ "bad value for root jitter: %s", optarg);
+ break;
+ case 'm':
+ defmailto = optarg;
+ break;
+ case 'n':
+ dont_daemonize = 1;
+ break;
+ case 'o':
+ dst_enabled = 0;
+ break;
+ case 's':
+ dst_enabled = 1;
+ break;
+ case 'x':
+ if (!set_debug_flags(optarg))
+ usage();
+ break;
+ default:
+ usage();
+ }
+ }
+}
+
+static int
+run_at_secres(cron_db *db)
+{
+ user *u;
+ entry *e;
+
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ if ((e->flags & (SEC_RES | INTERVAL)) != 0)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void
+find_interval_entry(pid_t pid)
+{
+ user *u;
+ entry *e;
+
+ for (u = database.head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ if ((e->flags & INTERVAL) && e->child == pid) {
+ e->lastexit = time(NULL);
+ e->child = 0;
+ break;
+ }
+ }
+ }
+}
diff --git a/usr.sbin/cron/cron/cron.h b/usr.sbin/cron/cron/cron.h
new file mode 100644
index 000000000000..67d91b57750e
--- /dev/null
+++ b/usr.sbin/cron/cron/cron.h
@@ -0,0 +1,37 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/* cron.h - header for vixie's cron
+ *
+ * $Id: cron.h,v 1.3 1998/08/14 00:32:37 vixie Exp $
+ *
+ * vix 14nov88 [rest of log is in RCS]
+ * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley]
+ * vix 30dec86 [written]
+ */
+
+#include "config.h"
+#include "externs.h"
+#include "pathnames.h"
+#include "macros.h"
+#include "structs.h"
+#include "funcs.h"
+#include "globals.h"
diff --git a/usr.sbin/cron/cron/crontab b/usr.sbin/cron/cron/crontab
new file mode 100644
index 000000000000..e37d3fd67543
--- /dev/null
+++ b/usr.sbin/cron/cron/crontab
@@ -0,0 +1,22 @@
+# /etc/crontab - system crontab for FreeBSD
+#
+#
+SHELL=/bin/sh
+PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
+#
+#minute hour mday month wday who command
+#
+# Save some entropy so that /dev/random can re-seed on boot.
+*/11 * * * * operator /usr/libexec/save-entropy
+#
+# Rotate log files every hour, if necessary.
+0 * * * * root newsyslog
+#
+# Perform daily/weekly/monthly maintenance.
+1 3 * * * root periodic daily
+15 4 * * 6 root periodic weekly
+30 5 1 * * root periodic monthly
+#
+# Adjust the time zone if the CMOS clock keeps local time, as opposed to
+# UTC time. See adjkerntz(8) for details.
+1,31 0-5 * * * root adjkerntz -a
diff --git a/usr.sbin/cron/cron/database.c b/usr.sbin/cron/cron/database.c
new file mode 100644
index 000000000000..35e5fad3524d
--- /dev/null
+++ b/usr.sbin/cron/cron/database.c
@@ -0,0 +1,316 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$Id: database.c,v 1.3 1998/08/14 00:32:38 vixie Exp $";
+#endif
+
+/* vix 26jan87 [RCS has the log]
+ */
+
+#include "cron.h"
+
+#define TMAX(a,b) ((a)>(b)?(a):(b))
+
+static void process_crontab(const char *, const char *,
+ const char *, struct stat *,
+ cron_db *, cron_db *);
+
+void
+load_database(cron_db *old_db)
+{
+ struct stat statbuf, syscron_stat, st;
+ cron_db new_db;
+ DIR_T *dp;
+ DIR *dir;
+ user *u, *nu;
+ time_t maxmtime;
+ struct {
+ const char *name;
+ struct stat st;
+ } syscrontabs [] = {
+ { SYSCRONTABS },
+ { LOCALSYSCRONTABS }
+ };
+ int i, ret;
+
+ 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;
+
+ maxmtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime);
+
+ for (i = 0; i < nitems(syscrontabs); i++) {
+ if (stat(syscrontabs[i].name, &syscrontabs[i].st) != -1) {
+ maxmtime = TMAX(syscrontabs[i].st.st_mtime, maxmtime);
+ /* Traverse into directory */
+ if (!(dir = opendir(syscrontabs[i].name)))
+ continue;
+ while (NULL != (dp = readdir(dir))) {
+ if (dp->d_name[0] == '.')
+ continue;
+ ret = fstatat(dirfd(dir), dp->d_name, &st, 0);
+ if (ret != 0 || !S_ISREG(st.st_mode))
+ continue;
+ maxmtime = TMAX(st.st_mtime, maxmtime);
+ }
+ closedir(dir);
+ } else {
+ syscrontabs[i].st.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 == maxmtime) {
+ 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 = maxmtime;
+ new_db.head = new_db.tail = NULL;
+
+ if (syscron_stat.st_mtime) {
+ process_crontab("root", SYS_NAME,
+ SYSCRONTAB, &syscron_stat,
+ &new_db, old_db);
+ }
+
+ for (i = 0; i < nitems(syscrontabs); i++) {
+ char tabname[MAXPATHLEN];
+ if (syscrontabs[i].st.st_mtime == 0)
+ continue;
+ if (!(dir = opendir(syscrontabs[i].name))) {
+ log_it("CRON", getpid(), "OPENDIR FAILED",
+ syscrontabs[i].name);
+ (void) exit(ERROR_EXIT);
+ }
+
+ while (NULL != (dp = readdir(dir))) {
+ if (dp->d_name[0] == '.')
+ continue;
+ if (fstatat(dirfd(dir), dp->d_name, &st, 0) == 0 &&
+ !S_ISREG(st.st_mode))
+ continue;
+ snprintf(tabname, sizeof(tabname), "%s/%s",
+ syscrontabs[i].name, dp->d_name);
+ process_crontab("root", SYS_NAME, tabname,
+ &syscrontabs[i].st, &new_db, old_db);
+ }
+ closedir(dir);
+ }
+
+ /* 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) strncpy(fname, dp->d_name, sizeof(fname));
+ fname[sizeof(fname)-1] = '\0';
+
+ if (snprintf(tabname, sizeof tabname, CRON_TAB(fname))
+ >= sizeof(tabname))
+ continue; /* XXX log? */
+
+ 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(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(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(cron_db *db, const char *name)
+{
+ user *u;
+
+ for (u = db->head; u != NULL; u = u->next)
+ if (strcmp(u->name, name) == 0)
+ break;
+ return (u);
+}
+
+static void
+process_crontab(const char *uname, const char *fname, const char *tabname,
+ struct stat *statbuf, cron_db *new_db, cron_db *old_db)
+{
+ struct passwd *pw = NULL;
+ int crontab_fd = OK - 1;
+ user *u;
+ entry *e;
+ time_t now;
+
+ if (strcmp(fname, SYS_NAME) != 0 && !(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;
+ /*
+ * TargetTime == 0 when we're initially populating the database,
+ * and TargetTime > 0 any time after that (i.e. we're reloading
+ * cron.d/ files because they've been created/modified). In the
+ * latter case, we should check for any interval jobs and run
+ * them 'n' seconds from the time the job was loaded/reloaded.
+ * Otherwise, they will not be run until cron is restarted.
+ */
+ if (TargetTime != 0) {
+ now = time(NULL);
+ for (e = u->crontab; e != NULL; e = e->next) {
+ if ((e->flags & INTERVAL) != 0)
+ e->lastexit = now;
+ }
+ }
+ link_user(new_db, u);
+ }
+
+ next_crontab:
+ if (crontab_fd >= OK) {
+ Debug(DLOAD, (" [done]\n"))
+ close(crontab_fd);
+ }
+}
diff --git a/usr.sbin/cron/cron/do_command.c b/usr.sbin/cron/cron/do_command.c
new file mode 100644
index 000000000000..43b3269d3087
--- /dev/null
+++ b/usr.sbin/cron/cron/do_command.c
@@ -0,0 +1,667 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$Id: do_command.c,v 1.3 1998/08/14 00:32:39 vixie Exp $";
+#endif
+
+#include "cron.h"
+#if defined(LOGIN_CAP)
+# include <login_cap.h>
+#endif
+#ifdef PAM
+# include <security/pam_appl.h>
+# include <security/openpam.h>
+#endif
+
+static void child_process(entry *, user *);
+static WAIT_T wait_on_child(PID_T, const char *);
+
+extern char *environ;
+
+void
+do_command(entry *e, user *u)
+{
+ pid_t pid;
+
+ 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.
+ */
+ switch ((pid = fork())) {
+ case -1:
+ log_it("CRON", getpid(), "error", "can't fork");
+ if (e->flags & INTERVAL)
+ e->lastexit = time(NULL);
+ break;
+ case 0:
+ /* child process */
+ pidfile_close(pfh);
+ child_process(e, u);
+ Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
+ _exit(OK_EXIT);
+ break;
+ default:
+ /* parent process */
+ Debug(DPROC, ("[%d] main process forked child #%d, "
+ "returning to work\n", getpid(), pid))
+ if (e->flags & INTERVAL) {
+ e->lastexit = 0;
+ e->child = pid;
+ }
+ break;
+ }
+ Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
+}
+
+
+static void
+child_process(entry *e, user *u)
+{
+ int stdin_pipe[2], stdout_pipe[2];
+ char *input_data;
+ const char *usernm, *mailto, *mailfrom;
+ PID_T jobpid, stdinjob, mailpid;
+ FILE *mail;
+ int bytes = 1;
+ int status = 0;
+ const char *homedir = NULL;
+# if defined(LOGIN_CAP)
+ struct passwd *pwd;
+ login_cap_t *lc;
+# endif
+
+ 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.
+ */
+ setproctitle("running job");
+
+ /* discover some useful and important environment settings
+ */
+ usernm = env_get("LOGNAME", e->envp);
+ mailto = env_get("MAILTO", e->envp);
+ mailfrom = env_get("MAILFROM", e->envp);
+
+#ifdef PAM
+ /* use PAM to see if the user's account is available,
+ * i.e., not locked or expired or whatever. skip this
+ * for system tasks from /etc/crontab -- they can run
+ * as any user.
+ */
+ if (strcmp(u->name, SYS_NAME)) { /* not equal */
+ pam_handle_t *pamh = NULL;
+ int pam_err;
+ struct pam_conv pamc = {
+ .conv = openpam_nullconv,
+ .appdata_ptr = NULL
+ };
+
+ Debug(DPROC, ("[%d] checking account with PAM\n", getpid()))
+
+ /* u->name keeps crontab owner name while LOGNAME is the name
+ * of user to run command on behalf of. they should be the
+ * same for a task from a per-user crontab.
+ */
+ if (strcmp(u->name, usernm)) {
+ log_it(usernm, getpid(), "username ambiguity", u->name);
+ exit(ERROR_EXIT);
+ }
+
+ pam_err = pam_start("cron", usernm, &pamc, &pamh);
+ if (pam_err != PAM_SUCCESS) {
+ log_it("CRON", getpid(), "error", "can't start PAM");
+ exit(ERROR_EXIT);
+ }
+
+ pam_err = pam_acct_mgmt(pamh, PAM_SILENT);
+ /* Expired password shouldn't prevent the job from running. */
+ if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) {
+ log_it(usernm, getpid(), "USER", "account unavailable");
+ exit(ERROR_EXIT);
+ }
+
+ pam_end(pamh, pam_err);
+ }
+#endif
+
+ /* 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() explicitly. so we have to disable the signal (which
+ * was inherited from the parent).
+ */
+ (void) signal(SIGCHLD, SIG_DFL);
+
+ /* create some pipes to talk to our future child
+ */
+ if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) {
+ log_it("CRON", getpid(), "error", "can't pipe");
+ exit(ERROR_EXIT);
+ }
+
+ /* 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.
+ *
+ * If there are escaped %'s, remove the escape character.
+ */
+ /*local*/{
+ int escaped = FALSE;
+ int ch;
+ char *p;
+
+ for (input_data = p = e->cmd;
+ (ch = *input_data) != '\0';
+ input_data++, p++) {
+ if (p != input_data)
+ *p = ch;
+ if (escaped) {
+ if (ch == '%' || ch == '\\')
+ *--p = ch;
+ escaped = FALSE;
+ continue;
+ }
+ if (ch == '\\') {
+ escaped = TRUE;
+ continue;
+ }
+ if (ch == '%') {
+ *input_data++ = '\0';
+ break;
+ }
+ }
+ *p = '\0';
+ }
+
+ /* fork again, this time so we can exec the user's command.
+ */
+ switch (jobpid = fork()) {
+ case -1:
+ log_it("CRON", getpid(), "error", "can't fork");
+ exit(ERROR_EXIT);
+ /*NOTREACHED*/
+ case 0:
+ Debug(DPROC, ("[%d] grandchild process fork()'ed\n",
+ getpid()))
+
+ if (e->uid == ROOT_UID)
+ Jitter = RootJitter;
+ if (Jitter != 0) {
+ srandom(getpid());
+ sleep(random() % Jitter);
+ }
+
+ /* 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.
+ */
+ if ((e->flags & DONT_LOG) == 0) {
+ 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]);
+
+ environ = NULL;
+
+# if defined(LOGIN_CAP)
+ /* Set user's entire context, but note that PATH will
+ * be overridden later
+ */
+ if ((pwd = getpwnam(usernm)) == NULL)
+ pwd = getpwuid(e->uid);
+ lc = NULL;
+ if (pwd != NULL) {
+ if (pwd->pw_dir != NULL
+ && pwd->pw_dir[0] != '\0') {
+ homedir = strdup(pwd->pw_dir);
+ if (homedir == NULL) {
+ warn("strdup");
+ _exit(ERROR_EXIT);
+ }
+ }
+ pwd->pw_gid = e->gid;
+ if (e->class != NULL)
+ lc = login_getclass(e->class);
+ }
+ if (pwd &&
+ setusercontext(lc, pwd, e->uid,
+ LOGIN_SETALL) == 0)
+ (void) endpwent();
+ else {
+ /* fall back to the old method */
+ (void) endpwent();
+# endif
+ /* set our directory, uid and gid. Set gid first,
+ * since once we set uid, we've lost root privileges.
+ */
+ if (setgid(e->gid) != 0) {
+ log_it(usernm, getpid(),
+ "error", "setgid failed");
+ _exit(ERROR_EXIT);
+ }
+ if (initgroups(usernm, e->gid) != 0) {
+ log_it(usernm, getpid(),
+ "error", "initgroups failed");
+ _exit(ERROR_EXIT);
+ }
+ if (setlogin(usernm) != 0) {
+ log_it(usernm, getpid(),
+ "error", "setlogin failed");
+ _exit(ERROR_EXIT);
+ }
+ if (setuid(e->uid) != 0) {
+ log_it(usernm, getpid(),
+ "error", "setuid failed");
+ _exit(ERROR_EXIT);
+ }
+ /* we aren't root after this..*/
+#if defined(LOGIN_CAP)
+ }
+ if (lc != NULL)
+ login_close(lc);
+#endif
+
+ /* For compatibility, we chdir to the value of HOME if it was
+ * specified explicitly in the crontab file, but not if it was
+ * set in the environment by some other mechanism. We chdir to
+ * the homedir given by the pw entry otherwise.
+ *
+ * If !LOGIN_CAP, then HOME is always set in e->envp.
+ *
+ * XXX: probably should also consult PAM.
+ */
+ {
+ char *new_home = env_get("HOME", e->envp);
+ if (new_home != NULL && new_home[0] != '\0')
+ chdir(new_home);
+ else if (homedir != NULL)
+ chdir(homedir);
+ else
+ chdir("/");
+ }
+
+ /* exec the command. Note that SHELL is not respected from
+ * either login.conf or pw_shell, only an explicit setting
+ * in the crontab. (default of _PATH_BSHELL is supplied when
+ * setting up the entry)
+ */
+ {
+ char *shell = env_get("SHELL", e->envp);
+ char **p;
+
+ /* Apply the environment from the entry, overriding
+ * existing values (this will always set LOGNAME and
+ * SHELL). putenv should not fail unless malloc does.
+ */
+ for (p = e->envp; *p; ++p) {
+ if (putenv(*p) != 0) {
+ warn("putenv");
+ _exit(ERROR_EXIT);
+ }
+ }
+
+ /* HOME in login.conf overrides pw, and HOME in the
+ * crontab overrides both. So set pw's value only if
+ * nothing was already set (overwrite==0).
+ */
+ if (homedir != NULL
+ && setenv("HOME", homedir, 0) < 0) {
+ warn("setenv(HOME)");
+ _exit(ERROR_EXIT);
+ }
+
+ /* PATH in login.conf is respected, but the crontab
+ * overrides; set a default value only if nothing
+ * already set.
+ */
+ if (setenv("PATH", _PATH_DEFPATH, 0) < 0) {
+ warn("setenv(PATH)");
+ _exit(ERROR_EXIT);
+ }
+
+# 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*/
+ execl(shell, shell, "-c", e->cmd, (char *)NULL);
+ warn("execl: couldn't exec `%s'", shell);
+ _exit(ERROR_EXIT);
+ }
+ break;
+ default:
+ /* parent process */
+ break;
+ }
+
+ /* 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 && (stdinjob = fork()) == 0) {
+ FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
+ int need_newline = FALSE;
+ int escaped = FALSE;
+ int ch;
+
+ if (out == NULL) {
+ warn("fdopen failed in child2");
+ _exit(ERROR_EXIT);
+ }
+
+ 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++) != '\0') {
+ 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]);
+
+ /*
+ * 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*/{
+ FILE *in = fdopen(stdout_pipe[READ_PIPE], "r");
+ int ch;
+
+ if (in == NULL) {
+ warn("fdopen failed in child");
+ _exit(ERROR_EXIT);
+ }
+
+ mail = NULL;
+
+ ch = getc(in);
+ if (ch != EOF) {
+ 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 == NULL) {
+ /* MAILTO not present, set to USER,
+ * unless globally overridden.
+ */
+ if (defmailto)
+ mailto = defmailto;
+ else
+ mailto = usernm;
+ }
+ if (mailto && *mailto == '\0')
+ mailto = NULL;
+
+ /* 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) {
+ char **env;
+ char mailcmd[MAX_COMMAND];
+ char hostname[MAXHOSTNAMELEN];
+
+ if (gethostname(hostname, MAXHOSTNAMELEN) == -1)
+ hostname[0] = '\0';
+ hostname[sizeof(hostname) - 1] = '\0';
+ if (snprintf(mailcmd, sizeof(mailcmd), MAILFMT,
+ MAILARG) >= sizeof(mailcmd)) {
+ warnx("mail command too long");
+ (void) _exit(ERROR_EXIT);
+ }
+ if (!(mail = cron_popen(mailcmd, "w", e, &mailpid))) {
+ warn("%s", mailcmd);
+ (void) _exit(ERROR_EXIT);
+ }
+ if (mailfrom == NULL || *mailfrom == '\0')
+ fprintf(mail, "From: Cron Daemon <%s@%s>\n",
+ usernm, hostname);
+ else
+ fprintf(mail, "From: Cron Daemon <%s>\n",
+ mailfrom);
+ fprintf(mail, "To: %s\n", mailto);
+ fprintf(mail, "Subject: Cron <%s@%s> %s\n",
+ usernm, first_word(hostname, "."),
+ e->cmd);
+#ifdef 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 (mail)
+ putc(ch, mail);
+ }
+ }
+ /*if data from grandchild*/
+
+ Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
+
+ /* also closes stdout_pipe[READ_PIPE] */
+ fclose(in);
+ }
+
+ /* wait for children to die.
+ */
+ if (jobpid > 0) {
+ WAIT_T waiter;
+
+ waiter = wait_on_child(jobpid, "grandchild command job");
+
+ /* If everything went well, and -n was set, _and_ we have mail,
+ * we won't be mailing... so shoot the messenger!
+ */
+ if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0
+ && (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR
+ && mail) {
+ Debug(DPROC, ("[%d] %s executed successfully, mail suppressed\n",
+ getpid(), "grandchild command job"))
+ kill(mailpid, SIGKILL);
+ (void)fclose(mail);
+ mail = NULL;
+ }
+
+ /* only close pipe if we opened it -- i.e., we're
+ * mailing...
+ */
+
+ if (mail) {
+ 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 (status) {
+ char buf[MAX_TEMPSTR];
+
+ snprintf(buf, sizeof(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 (*input_data && stdinjob > 0)
+ wait_on_child(stdinjob, "grandchild stdinjob");
+}
+
+static WAIT_T
+wait_on_child(PID_T childpid, const char *name)
+{
+ WAIT_T waiter;
+ PID_T pid;
+
+ Debug(DPROC, ("[%d] waiting for %s (%d) to finish\n",
+ getpid(), name, childpid))
+
+#ifdef POSIX
+ while ((pid = waitpid(childpid, &waiter, 0)) < 0 && errno == EINTR)
+#else
+ while ((pid = wait4(childpid, &waiter, 0, NULL)) < 0 && errno == EINTR)
+#endif
+ ;
+
+ if (pid < OK)
+ return waiter;
+
+ Debug(DPROC, ("[%d] %s (%d) finished, status=%04x",
+ getpid(), name, pid, WEXITSTATUS(waiter)))
+ if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
+ Debug(DPROC, (", dumped core"))
+ Debug(DPROC, ("\n"))
+
+ return waiter;
+}
diff --git a/usr.sbin/cron/cron/externs.h b/usr.sbin/cron/cron/externs.h
new file mode 100644
index 000000000000..343848240f4f
--- /dev/null
+++ b/usr.sbin/cron/cron/externs.h
@@ -0,0 +1,128 @@
+/* Copyright 1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/* reorder these #include's at your peril */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <sys/fcntl.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <libutil.h>
+#include <locale.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+
+#if defined(SYSLOG)
+# include <syslog.h>
+#endif
+
+#if (defined(BSD)) && (BSD >= 199103) || defined(__linux) || defined(AIX)
+# include <paths.h>
+#endif /*BSD*/
+
+#if !defined(_PATH_SENDMAIL)
+# define _PATH_SENDMAIL "/usr/lib/sendmail"
+#endif /*SENDMAIL*/
+
+#if defined(__bsdi__) && (_BSDI_VERSION > 199510)
+#include <login_cap.h>
+#endif /* __bsdi__ */
+
+#define DIR_T struct dirent
+#define WAIT_T int
+#define SIG_T sig_t
+#define TIME_T time_t
+#define PID_T pid_t
+
+#ifndef TZNAME_ALREADY_DEFINED
+extern char *tzname[2];
+#endif
+#define TZONE(tm) tzname[(tm).tm_isdst]
+
+#if (BSD >= 198606)
+# define HAVE_FCHOWN
+# define HAVE_FCHMOD
+#endif
+
+#if (BSD >= 199103)
+# define HAVE_SAVED_UIDS
+#endif
+
+#define MY_UID(pw) getuid()
+#define MY_GID(pw) getgid()
+
+#if !defined(AIX) && !defined(UNICOS)
+# define SYS_TIME_H 1
+#else
+# define SYS_TIME_H 0
+#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))
+int getopt(int, char * const *, const char *);
+#endif
+
+#if (!defined(BSD) || (BSD < 199103))
+extern char *optarg;
+extern int optind, opterr, optopt;
+#endif
+
+/* digital unix needs this but does not give us a way to identify it.
+ */
+extern int flock(int, int);
+
+/* not all systems who provice flock() provide these definitions.
+ */
+#ifndef LOCK_SH
+# define LOCK_SH 1
+#endif
+#ifndef LOCK_EX
+# define LOCK_EX 2
+#endif
+#ifndef LOCK_NB
+# define LOCK_NB 4
+#endif
+#ifndef LOCK_UN
+# define LOCK_UN 8
+#endif
diff --git a/usr.sbin/cron/cron/funcs.h b/usr.sbin/cron/cron/funcs.h
new file mode 100644
index 000000000000..8a29513a0b31
--- /dev/null
+++ b/usr.sbin/cron/cron/funcs.h
@@ -0,0 +1,70 @@
+/*
+ * $Id: funcs.h,v 1.1 1998/08/14 00:31:24 vixie Exp $
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/* Notes:
+ * This file has to be included by cron.h after data structure defs.
+ * We should reorg this into sections by module.
+ */
+
+void set_cron_uid(void),
+ set_cron_cwd(void),
+ load_database(cron_db *),
+ open_logfile(void),
+ sigpipe_func(void),
+ job_add(entry *, user *),
+ do_command(entry *, user *),
+ link_user(cron_db *, user *),
+ unlink_user(cron_db *, user *),
+ free_user(user *),
+ env_free(char **),
+ unget_char(int, FILE *),
+ free_entry(entry *),
+ skip_comments(FILE *),
+ log_it(const char *, int, const char *, const char *),
+ log_close(void);
+
+int job_runqueue(void),
+ set_debug_flags(char *),
+ get_char(FILE *),
+ get_string(char *, int, FILE *, char *),
+ swap_uids(void),
+ swap_uids_back(void),
+ load_env(char *, FILE *),
+ cron_pclose(FILE *),
+ strcmp_until(const char *, const char *, int),
+ allowed(char *),
+ strdtb(char *);
+
+char *env_get(char *, char **),
+ *arpadate(time_t *),
+ *mkprints(unsigned char *, unsigned int),
+ *first_word(char *, char *),
+ **env_init(void),
+ **env_copy(char **),
+ **env_set(char **, char *);
+
+user *load_user(int, struct passwd *, const char *),
+ *find_user(cron_db *, const char *);
+
+entry *load_entry(FILE *, void (*)(const char *),
+ struct passwd *, char **);
+
+FILE *cron_popen(char *, char *, entry *, PID_T *);
diff --git a/usr.sbin/cron/cron/globals.h b/usr.sbin/cron/cron/globals.h
new file mode 100644
index 000000000000..77b9bf7798ae
--- /dev/null
+++ b/usr.sbin/cron/cron/globals.h
@@ -0,0 +1,75 @@
+/*
+ * $Id: globals.h,v 1.1 1998/08/14 00:31:23 vixie Exp $
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+#ifdef MAIN_PROGRAM
+# define XTRN
+# define INIT(x) = x
+#else
+# define XTRN extern
+# define INIT(x)
+#endif
+
+XTRN const char *copyright[]
+#ifdef MAIN_PROGRAM
+ = {
+ NULL
+ }
+#endif
+ ;
+
+XTRN const char *MonthNames[]
+#ifdef MAIN_PROGRAM
+ = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",\
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",\
+ NULL
+ }
+#endif
+ ;
+
+XTRN const char *DowNames[]
+#ifdef MAIN_PROGRAM
+ = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",\
+ NULL
+ }
+#endif
+ ;
+
+XTRN const char *ProgramName INIT("amnesia");
+XTRN const char *defmailto;
+XTRN int LineNumber INIT(0);
+XTRN unsigned Jitter;
+XTRN unsigned RootJitter;
+XTRN time_t TargetTime INIT(0);
+XTRN struct pidfh *pfh;
+
+#if DEBUGGING
+XTRN int DebugFlags INIT(0);
+XTRN const char *DebugFlagNames[]
+#ifdef MAIN_PROGRAM
+ = {
+ "ext", "sch", "proc", "pars", "load", "misc", "test", "bit",\
+ NULL
+ }
+#endif
+ ;
+#endif /* DEBUGGING */
diff --git a/usr.sbin/cron/cron/job.c b/usr.sbin/cron/cron/job.c
new file mode 100644
index 000000000000..9579cd9d755d
--- /dev/null
+++ b/usr.sbin/cron/cron/job.c
@@ -0,0 +1,77 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+
+
+#include "cron.h"
+
+
+typedef struct _job {
+ struct _job *next;
+ entry *e;
+ user *u;
+} job;
+
+
+static job *jhead = NULL, *jtail = NULL;
+
+
+void
+job_add(entry *e, user *u)
+{
+ job *j;
+
+ /* if already on queue, keep going */
+ for (j = jhead; j != NULL; j = j->next)
+ if (j->e == e && j->u == u)
+ return;
+
+ /* build a job queue element */
+ if ((j = (job*)malloc(sizeof(job))) == NULL)
+ return;
+ j->next = (job*) NULL;
+ j->e = e;
+ j->u = u;
+
+ /* add it to the tail */
+ if (jhead == NULL)
+ jhead = j;
+ else
+ jtail->next = j;
+ jtail = j;
+}
+
+
+int
+job_runqueue(void)
+{
+ job *j, *jn;
+ 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/cron/macros.h b/usr.sbin/cron/cron/macros.h
new file mode 100644
index 000000000000..6ce680818b31
--- /dev/null
+++ b/usr.sbin/cron/cron/macros.h
@@ -0,0 +1,134 @@
+/*
+ * $Id: macros.h,v 1.1 1998/08/14 00:31:24 vixie Exp $
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+ /* 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 ROOT_UID 0 /* don't change this, it really must be root */
+#define ROOT_USER "root" /* ditto */
+#define SYS_NAME "*system*" /* magic owner name for system crontab */
+
+ /* 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 PPC_NULL ((const char **)NULL)
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#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_SECOND 0
+#define LAST_SECOND 59
+#define SECOND_COUNT (LAST_SECOND - FIRST_SECOND + 1)
+
+#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)
+
+#ifdef LOGIN_CAP
+/* see init.c */
+#define RESOURCE_RC "daemon"
+#endif
+
+ /* each user's crontab will be held as a list of
+ * the following structure.
+ *
+ * These are the cron commands.
+ */
+
diff --git a/usr.sbin/cron/cron/pathnames.h b/usr.sbin/cron/cron/pathnames.h
new file mode 100644
index 000000000000..2906d8728692
--- /dev/null
+++ b/usr.sbin/cron/cron/pathnames.h
@@ -0,0 +1,69 @@
+/* Copyright 1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/*
+ * $Id: pathnames.h,v 1.4 1998/08/14 00:32:41 vixie Exp $
+ */
+
+#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. Note that if
+ * LOG_CRON is defined by <syslog.h>, LOG_FILE will not
+ * be used.
+ */
+#define ALLOW_FILE "allow"
+#define DENY_FILE "deny"
+#define LOG_FILE "log"
+
+ /* where should the daemon stick its PID?
+ */
+#define PIDDIR _PATH_VARRUN
+#define PIDFILE "cron.pid"
+
+ /* 4.3BSD-style crontab */
+#define SYSCRONTAB "/etc/crontab"
+#define SYSCRONTABS "/etc/cron.d"
+#define LOCALSYSCRONTABS _PATH_LOCALBASE "/etc/cron.d"
+
+ /* what editor to use if no EDITOR or VISUAL
+ * environment variable specified.
+ */
+#define EDITOR _PATH_VI
diff --git a/usr.sbin/cron/cron/popen.c b/usr.sbin/cron/cron/popen.c
new file mode 100644
index 000000000000..abd9de198e87
--- /dev/null
+++ b/usr.sbin/cron/cron/popen.c
@@ -0,0 +1,238 @@
+/*
+ * SPDX-License-Identifier: BSD-4.3TAHOE
+ *
+ * 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 const char rcsid[] =
+ "$Id: popen.c,v 1.3 1998/08/14 00:32:41 vixie Exp $";
+#endif /* not lint */
+
+#include "cron.h"
+#if defined(LOGIN_CAP)
+# include <login_cap.h>
+#endif
+
+#define MAX_ARGS 100
+#define WANT_GLOBBING 0
+
+/*
+ * Special version of popen which avoids call to shell. This insures no one
+ * 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(char *program, char *type, entry *e, PID_T *pidptr)
+{
+ char *cp;
+ FILE *iop;
+ int argc, pdes[2];
+ PID_T pid;
+ char *usernm;
+ char *argv[MAX_ARGS + 1];
+# if defined(LOGIN_CAP)
+ struct passwd *pwd;
+ login_cap_t *lc;
+# endif
+#if WANT_GLOBBING
+ char **pop, *vv[2];
+ int gargc;
+ char *gargv[1000];
+ extern char **glob(), **copyblk();
+#endif
+
+ if ((*type != 'r' && *type != 'w') || type[1] != '\0')
+ return (NULL);
+
+ if (!pids) {
+ if ((fds = sysconf(_SC_OPEN_MAX)) <= 0)
+ return (NULL);
+ if (!(pids = calloc(fds, sizeof(PID_T))))
+ return (NULL);
+ }
+ 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;
+ argv[MAX_ARGS] = NULL;
+
+#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 = fork()) {
+ case -1: /* error */
+ (void)close(pdes[0]);
+ (void)close(pdes[1]);
+ goto pfree;
+ /* NOTREACHED */
+ case 0: /* child */
+ if (e != NULL) {
+#ifdef SYSLOG
+ closelog();
+#endif
+
+ /* get new pgrp, void tty, etc.
+ */
+ (void) setsid();
+ }
+ if (*type == 'r') {
+ /* Do not share our parent's stdin */
+ (void)close(0);
+ (void)open(_PATH_DEVNULL, O_RDWR);
+ 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]);
+ }
+ /* Hack: stdout gets revoked */
+ (void)close(1);
+ (void)open(_PATH_DEVNULL, O_RDWR);
+ (void)close(2);
+ (void)open(_PATH_DEVNULL, O_RDWR);
+ (void)close(pdes[1]);
+ }
+ if (e != NULL) {
+ /* Set user's entire context, but skip the environment
+ * as cron provides a separate interface for this
+ */
+ usernm = env_get("LOGNAME", e->envp);
+# if defined(LOGIN_CAP)
+ if ((pwd = getpwnam(usernm)) == NULL)
+ pwd = getpwuid(e->uid);
+ lc = NULL;
+ if (pwd != NULL) {
+ pwd->pw_gid = e->gid;
+ if (e->class != NULL)
+ lc = login_getclass(e->class);
+ }
+ if (pwd &&
+ setusercontext(lc, pwd, e->uid,
+ LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
+ (void) endpwent();
+ else {
+ /* fall back to the old method */
+ (void) endpwent();
+# endif
+ /*
+ * Set our directory, uid and gid. Set gid
+ * first since once we set uid, we've lost
+ * root privileges.
+ */
+ if (setgid(e->gid) != 0)
+ _exit(ERROR_EXIT);
+# if defined(BSD)
+ if (initgroups(usernm, e->gid) != 0)
+ _exit(ERROR_EXIT);
+# endif
+ if (setlogin(usernm) != 0)
+ _exit(ERROR_EXIT);
+ if (setuid(e->uid) != 0)
+ _exit(ERROR_EXIT);
+ /* we aren't root after this..*/
+#if defined(LOGIN_CAP)
+ }
+ if (lc != NULL)
+ login_close(lc);
+#endif
+ chdir(env_get("HOME", e->envp));
+ }
+#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
+
+ *pidptr = pid;
+
+ return (iop);
+}
+
+int
+cron_pclose(FILE *iop)
+{
+ 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/cron/structs.h b/usr.sbin/cron/cron/structs.h
new file mode 100644
index 000000000000..c9644e95064d
--- /dev/null
+++ b/usr.sbin/cron/cron/structs.h
@@ -0,0 +1,81 @@
+/*
+ * $Id: structs.h,v 1.1 1998/08/14 00:31:24 vixie Exp $
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+typedef struct _entry {
+ struct _entry *next;
+ uid_t uid;
+ gid_t gid;
+#ifdef LOGIN_CAP
+ char *class;
+#endif
+ char **envp;
+ char *cmd;
+ union {
+ struct {
+ bitstr_t bit_decl(second, SECOND_COUNT);
+ 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);
+ };
+ struct {
+ time_t lastexit;
+ time_t interval;
+ pid_t child;
+ };
+ };
+ int flags;
+#define DOM_STAR 0x01
+#define DOW_STAR 0x02
+#define WHEN_REBOOT 0x04
+#define DONT_LOG 0x08
+#define NOT_UNTIL 0x10
+#define SEC_RES 0x20
+#define INTERVAL 0x40
+#define RUN_AT 0x80
+#define MAIL_WHEN_ERR 0x100
+ time_t lastrun;
+} 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;
+ /* in the C tradition, we only create
+ * variables for the main program, just
+ * extern them elsewhere.
+ */
+
diff --git a/usr.sbin/cron/cron/user.c b/usr.sbin/cron/cron/user.c
new file mode 100644
index 000000000000..35c4f684f013
--- /dev/null
+++ b/usr.sbin/cron/cron/user.c
@@ -0,0 +1,123 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+
+/* vix 26jan87 [log is in RCS file]
+ */
+
+#include "cron.h"
+
+static char *User_name;
+
+void
+free_user(user *u)
+{
+ entry *e, *ne;
+
+ free(u->name);
+ for (e = u->crontab; e != NULL; e = ne) {
+ ne = e->next;
+ free_entry(e);
+ }
+ free(u);
+}
+
+static void
+log_error(const char *msg)
+{
+ log_it(User_name, getpid(), "PARSE", msg);
+}
+
+/* NULL pw implies syscrontab */
+user *
+load_user(int crontab_fd, struct passwd *pw, const char *name)
+{
+ char envstr[MAX_ENVSTR];
+ FILE *file;
+ user *u;
+ entry *e;
+ int status;
+ char **envp, **tenvp;
+
+ if (!(file = fdopen(crontab_fd, "r"))) {
+ warn("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.
+ */
+ if ((u = (user *) malloc(sizeof(user))) == NULL) {
+ errno = ENOMEM;
+ return (NULL);
+ }
+ if ((u->name = strdup(name)) == NULL) {
+ free(u);
+ errno = ENOMEM;
+ return (NULL);
+ }
+ u->crontab = NULL;
+
+ /*
+ * init environment. this will be copied/augmented for each entry.
+ */
+ if ((envp = env_init()) == NULL) {
+ free(u->name);
+ free(u);
+ return (NULL);
+ }
+
+ /*
+ * load the crontab
+ */
+ while ((status = load_env(envstr, file)) >= OK) {
+ switch (status) {
+ case ERR:
+ free_user(u);
+ u = NULL;
+ goto done;
+ case FALSE:
+ User_name = u->name; /* for log_error */
+ e = load_entry(file, log_error, pw, envp);
+ if (e) {
+ e->next = u->crontab;
+ u->crontab = e;
+ }
+ break;
+ case TRUE:
+ if ((tenvp = env_set(envp, envstr))) {
+ envp = tenvp;
+ } else {
+ free_user(u);
+ u = NULL;
+ goto done;
+ }
+ break;
+ }
+ }
+
+ done:
+ env_free(envp);
+ fclose(file);
+ Debug(DPARS, ("...load_user() done\n"))
+ return (u);
+}
diff --git a/usr.sbin/cron/crontab/Makefile b/usr.sbin/cron/crontab/Makefile
new file mode 100644
index 000000000000..8c60575e7d70
--- /dev/null
+++ b/usr.sbin/cron/crontab/Makefile
@@ -0,0 +1,16 @@
+BINDIR= /usr/bin
+
+PACKAGE=cron
+PROG= crontab
+MAN= crontab.1 crontab.5
+BINOWN= root
+BINMODE=4555
+PRECIOUSPROG=
+
+WARNS?= 3
+
+CFLAGS+= -I${.CURDIR:H}/cron
+
+LIBADD= cron md util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/cron/crontab/Makefile.depend b/usr.sbin/cron/crontab/Makefile.depend
new file mode 100644
index 000000000000..e1fa9e85dcb0
--- /dev/null
+++ b/usr.sbin/cron/crontab/Makefile.depend
@@ -0,0 +1,18 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+ lib/libutil \
+ usr.sbin/cron/lib \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/cron/crontab/crontab.1 b/usr.sbin/cron/crontab/crontab.1
new file mode 100644
index 000000000000..3919486c7c71
--- /dev/null
+++ b/usr.sbin/cron/crontab/crontab.1
@@ -0,0 +1,153 @@
+.\"/* Copyright 1988,1990,1993 by Paul Vixie
+.\" * All rights reserved
+.\" */
+.\"
+.\"Copyright (c) 1997 by Internet Software Consortium
+.\"
+.\"Permission to use, copy, modify, and distribute this software for any
+.\"purpose with or without fee is hereby granted, provided that the above
+.\"copyright notice and this permission notice appear in all copies.
+.\"
+.\"THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+.\"ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+.\"OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+.\"CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+.\"DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+.\"PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+.\"ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+.\"SOFTWARE.
+.\"
+.\" $Id: crontab.1,v 1.2 1998/08/14 00:32:37 vixie Exp $
+.\"
+.Dd December 20, 2016
+.Dt CRONTAB 1
+.Os
+.Sh NAME
+.Nm crontab
+.Nd maintain crontab files for individual users (V3)
+.Sh SYNOPSIS
+.Nm
+.Op Fl u Ar user
+.Ar file
+.Nm
+.Op Fl u Ar user
+{
+.Fl l |
+.Fl r Op Fl f
+|
+.Fl e
+}
+.Sh DESCRIPTION
+The
+.Nm
+utility is the program used to install, deinstall or list the tables
+used to drive the
+.Xr cron 8
+daemon in Vixie Cron.
+Each user can have their own crontab, and though
+these are files in
+.Pa /var/cron/tabs ,
+they are not intended to be edited directly.
+.Pp
+If the
+.Pa allow
+file exists, then you must be listed therein in order to be allowed to use
+this command.
+If the
+.Pa allow
+file does not exist but the
+.Pa deny
+file does exist, then you must
+.Em not
+be listed in the
+.Pa 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.
+The format of these files is one username per line,
+with no leading or trailing whitespace.
+Lines of other formats will be ignored,
+and so can be used for comments.
+.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
+.Sq Fl
+is given.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl u
+Specify the name of the user whose crontab is to be
+tweaked.
+If this option is not given,
+.Nm
+examines
+.Dq your
+crontab, i.e., the crontab of the person executing the
+command.
+Note that
+.Xr su 1
+can confuse
+.Nm
+and that if you are running inside of
+.Xr su 1
+you should always use the
+.Fl u
+option for safety's sake.
+.It Fl l
+Display the current crontab on standard output.
+.It Fl r
+Remove the current crontab.
+By default the
+.Fl r
+option prompts for confirmation, adding the
+.Fl f
+option will attempt to remove the current crontab without confirmation.
+.It Fl e
+Edit the current crontab using the editor specified by
+the
+.Ev VISUAL
+or
+.Ev EDITOR
+environment variables.
+The specified editor
+.Em must
+edit the file in place;
+any editor that unlinks the file and recreates it cannot be used.
+After you exit
+from the editor, the modified crontab will be installed automatically.
+.El
+.Sh FILES
+.Bl -tag -width /var/cron/allow -compact
+.It Pa /var/cron/allow
+List of users allowed to use crontab
+.It Pa /var/cron/deny
+List of users prohibited from using crontab
+.It Pa /var/cron/tabs
+Directory for personal crontab files
+.El
+.Sh DIAGNOSTICS
+A fairly informative usage message appears if you run it with a bad command
+line.
+.Sh SEE ALSO
+.Xr crontab 5 ,
+.Xr cron 8
+.Sh STANDARDS
+The
+.Nm
+command conforms to
+.St -p1003.2
+with the exception that the dangerous variant of calling
+.Nm
+without a file name in the first form of the command is not allowed by
+this implementation.
+The pseudo-filename
+.Sq Fl
+must be specified to read from standard input.
+The new command syntax
+differs from previous versions of Vixie Cron, as well as from the classic
+SVR3 syntax.
+.Sh AUTHORS
+.An Paul Vixie Aq Mt paul@vix.com
diff --git a/usr.sbin/cron/crontab/crontab.5 b/usr.sbin/cron/crontab/crontab.5
new file mode 100644
index 000000000000..e4e6fae0b01b
--- /dev/null
+++ b/usr.sbin/cron/crontab/crontab.5
@@ -0,0 +1,393 @@
+.\"/* Copyright 1988,1990,1993,1994 by Paul Vixie
+.\" * All rights reserved
+.\" */
+.\"
+.\"Copyright (c) 1997 by Internet Software Consortium
+.\"
+.\"Permission to use, copy, modify, and distribute this software for any
+.\"purpose with or without fee is hereby granted, provided that the above
+.\"copyright notice and this permission notice appear in all copies.
+.\"
+.\"THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+.\"ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+.\"OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+.\"CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+.\"DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+.\"PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+.\"ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+.\"SOFTWARE.
+.\"
+.\" $Id: crontab.5,v 1.2 1998/08/14 00:32:38 vixie Exp $
+.\"
+.Dd May 10, 2024
+.Dt CRONTAB 5
+.Os
+.Sh NAME
+.Nm crontab
+.Nd tables for driving cron
+.Sh DESCRIPTION
+A
+.Nm
+file contains instructions to the
+.Xr 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
+.Xr 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,
+.Bd -literal
+ name = value
+.Ed
+.Pp
+where the spaces around the equal-sign (=) are optional, and any subsequent
+non-leading spaces in
+.Em value
+will be part of the value assigned to
+.Em name .
+The
+.Em value
+string may be placed in quotes (single or double, but matching) to preserve
+leading or trailing blanks.
+The
+.Em name
+string may also be placed in quote (single or double, but matching)
+to preserve leading, trailing or inner blanks.
+.Pp
+Several environment variables are set up
+automatically by the
+.Xr cron 8
+daemon.
+.Ev SHELL
+is set to
+.Pa /bin/sh ,
+and
+.Ev LOGNAME
+and
+.Ev HOME
+are set from the
+.Pa /etc/passwd
+line of the crontab's owner.
+In addition, the environment variables of the
+user's login class will be set from
+.Pa /etc/login.conf.db
+and
+.Pa ~/.login_conf .
+(A setting of
+.Ev HOME
+in the login class will override the value from
+.Pa /etc/passwd ,
+but will not change the current directory when the command is
+invoked, which can only be overridden with an explicit setting of
+.Ev HOME
+within the crontab file itself.)
+If
+.Ev PATH
+is not set by any other means, it is defaulted to
+.Pa /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin .
+.Ev HOME ,
+.Ev PATH
+and
+.Ev SHELL ,
+and any variables set from the login class,
+may be overridden by settings in the crontab;
+.Ev LOGNAME
+may not.
+.Pp
+(Another note: the
+.Ev LOGNAME
+variable is sometimes called
+.Ev USER
+on
+.Bx
+systems...
+On these systems,
+.Ev USER
+will be set also).
+.Pp
+If
+.Xr cron 8
+has any reason to send mail as a result of running commands in
+``this'' crontab, it will respect the following settings which may be
+defined in the crontab (but which are not taken from the login class).
+If
+.Ev MAILTO
+is defined (and non-empty), mail is
+sent to the user so named.
+If
+.Ev MAILFROM
+is defined (and non-empty), its value will be used as the from address.
+.Ev MAILTO
+may also be used to direct mail to multiple recipients
+by separating recipient users with a comma.
+If
+.Ev 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
+.Pa /bin/mail
+instead of
+.Pa /usr/lib/sendmail
+as
+your mailer when you install cron --
+.Pa /bin/mail
+does not do aliasing, and UUCP
+usually does not read its mail.
+.Pp
+The format of a cron command is very much the V7 standard, with a number of
+upward-compatible extensions.
+.Pp
+Each user cron line has five time and date fields, followed by a command.
+.Pp
+Each line in system crontab (
+.Pa /etc/crontab, /etc/cron.d, /usr/local/etc/cron.d
+) has five time and date fields, followed by a valid user name
+(with optional ``:<group>'' and ``/<login-class>'' suffixes),
+followed by a command.
+.Pp
+Commands are executed by
+.Xr cron 8
+when the minute, hour, and month of year fields match the current time,
+.Em and
+when at least one of the two day fields (day of month, or day of week)
+matches the current time (see ``Note'' below).
+.Xr cron 8
+examines cron entries once every minute.
+The time and date fields are:
+.Bd -literal -offset indent
+field allowed values
+----- --------------
+minute 0-59
+hour 0-23
+day of month 1-31
+month 1-12 (or names, see below)
+day of week 0-7 (0 or 7 is Sun, or use names)
+.Ed
+.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 does not matter).
+Ranges and lists are also allowed.
+.Pp
+The ``sixth'' field (the rest of the line) specifies the command to be
+run.
+One or more command options may precede the command to modify processing
+behavior.
+The entire command portion of the line, up to a newline or %
+character, will be executed by
+.Pa /bin/sh
+or by the shell
+specified in the
+.Ev 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
+The following command options can be supplied:
+.Bl -tag -width Ds
+.It Fl n
+No mail is sent after a successful run.
+The execution output will only be mailed if the command exits with a non-zero
+exit code.
+The
+.Fl n
+option is an attempt to cure potentially copious volumes of mail coming from
+.Xr cron 8 .
+.It Fl q
+Execution will not be logged.
+.El
+.sp
+Duplicate options are not allowed.
+.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, are not *), the command will be run when
+.Em either
+field matches the current time.
+For example,
+``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.
+.Pp
+Instead of the first five fields,
+a line may start with
+.Sq @
+symbol followed either by one of eight special strings or by a numeric value.
+The recognized special strings are:
+.Bd -literal -offset indent
+string meaning
+------ -------
+@reboot Run once, at startup of cron.
+@yearly Run once a year, "0 0 1 1 *".
+@annually (same as @yearly)
+@monthly Run once a month, "0 0 1 * *".
+@weekly Run once a week, "0 0 * * 0".
+@daily Run once a day, "0 0 * * *".
+@midnight (same as @daily)
+@hourly Run once an hour, "0 * * * *".
+@every_minute Run once a minute, "*/1 * * * *".
+@every_second Run once a second.
+.Ed
+.Pp
+The
+.Sq @
+symbol followed by a numeric value has a special notion of running
+a job that many seconds after completion of the previous invocation of
+the job.
+Unlike regular syntax, it guarantees not to overlap two or more
+invocations of the same job during normal cron execution.
+Note, however, that overlap may occur if the job is running when the file
+containing the job is modified and subsequently reloaded.
+The first run is scheduled for the specified number of seconds after cron
+is started or the crontab entry is reloaded.
+.Sh EXAMPLE SYSTEM CRON FILE
+.Bd -literal
+# sample /etc/cron.d/vmstat
+# run vmstat every five minutes
+# note the username as sixth field!
+*/5 * * * * root vmstat
+.Ed
+.Sh EXAMPLE USER CRON FILE
+.Bd -literal
+# use /bin/sh to run commands, overriding the default set by cron
+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"
+# run at 5 minutes intervals, no matter how long it takes
+@300 svnlite up /usr/src
+# run every minute, suppress logging
+* * * * * -q date
+# run every minute, only send mail if ping fails
+* * * * * -n ping -c 1 freebsd.org
+.Ed
+.Sh SEE ALSO
+.Xr crontab 1 ,
+.Xr cron 8
+.Sh EXTENSIONS
+When specifying day of week, both day 0 and day 7 will be considered Sunday.
+.Bx
+and
+.Tn 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
+.Tn ATT
+or
+.Bx
+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
+.Bx
+or
+.Tn ATT ,
+the
+environment handed to child processes is basically the one from
+.Pa /etc/rc .
+.Pp
+Command output is mailed to the crontab owner
+.No ( Bx
+cannot do this), can be
+mailed to a person other than the crontab owner (SysV cannot do this), or the
+feature can be turned off and no mail will be sent at all (SysV cannot do this
+either).
+.Pp
+All of the
+.Sq @
+directives that can appear in place of the first five fields
+are extensions.
+.Pp
+Command processing can be modified using command options.
+The
+.Sq -q
+option suppresses logging.
+The
+.Sq -n
+option does not mail on successful run.
+.Sh AUTHORS
+.An Paul Vixie Aq Mt paul@vix.com
+.Sh BUGS
+If you are in one of the 70-odd countries that observe Daylight
+Savings Time, jobs scheduled during the rollback or advance may be
+affected if
+.Xr cron 8
+is not started with the
+.Fl s
+flag.
+In general, it is not a good idea to schedule jobs during
+this period if
+.Xr cron 8
+is not started with the
+.Fl s
+flag, which is enabled by default.
+See
+.Xr cron 8
+for more details.
+.Pp
+For US timezones (except parts of AZ and HI) the time shift occurs at
+2AM local time.
+For others, the output of the
+.Xr zdump 8
+program's verbose
+.Fl ( v )
+option can be used to determine the moment of time shift.
diff --git a/usr.sbin/cron/crontab/crontab.c b/usr.sbin/cron/crontab/crontab.c
new file mode 100644
index 000000000000..0975a2ae8c79
--- /dev/null
+++ b/usr.sbin/cron/crontab/crontab.c
@@ -0,0 +1,632 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$Id: crontab.c,v 1.3 1998/08/14 00:32:38 vixie 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 <md5.h>
+
+#define MD5_SIZE 33
+#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[MAXLOGNAME], RealUser[MAXLOGNAME];
+static char Filename[MAX_FNAME];
+static FILE *NewCrontab;
+static int CheckErrorCount;
+static enum opt_t Option;
+static int fflag;
+static struct passwd *pw;
+static void list_cmd(void),
+ delete_cmd(void),
+ edit_cmd(void),
+ poke_daemon(void),
+ check_error(const char *),
+ parse_args(int c, char *v[]);
+static int replace_cmd(void);
+
+static void
+usage(const char *msg)
+{
+ fprintf(stderr, "crontab: usage error: %s\n", msg);
+ fprintf(stderr, "%s\n%s\n",
+ "usage: crontab [-u user] file",
+ " crontab [-u user] { -l | -r [-f] | -e }");
+ exit(ERROR_EXIT);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int exitstatus;
+
+ Pid = getpid();
+ ProgramName = argv[0];
+
+ setlocale(LC_ALL, "");
+
+#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)) {
+ warnx("you (%s) are not allowed to use this program", User);
+ 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;
+ case opt_unknown:
+ default:
+ abort();
+ }
+ exit(exitstatus);
+ /*NOTREACHED*/
+}
+
+static void
+parse_args(int argc, char *argv[])
+{
+ int argch;
+ char resolved_path[PATH_MAX];
+
+ if (!(pw = getpwuid(getuid())))
+ errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out");
+ bzero(pw->pw_passwd, strlen(pw->pw_passwd));
+ (void) strncpy(User, pw->pw_name, (sizeof User)-1);
+ User[(sizeof User)-1] = '\0';
+ strcpy(RealUser, User);
+ Filename[0] = '\0';
+ Option = opt_unknown;
+ while ((argch = getopt(argc, argv, "u:lerx:f")) != -1) {
+ switch (argch) {
+ case 'x':
+ if (!set_debug_flags(optarg))
+ usage("bad debug option");
+ break;
+ case 'u':
+ if (getuid() != ROOT_UID)
+ errx(ERROR_EXIT, "must be privileged to use -u");
+ if (!(pw = getpwnam(optarg)))
+ errx(ERROR_EXIT, "user `%s' unknown", optarg);
+ bzero(pw->pw_passwd, strlen(pw->pw_passwd));
+ (void) strncpy(User, pw->pw_name, (sizeof User)-1);
+ User[(sizeof User)-1] = '\0';
+ 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;
+ case 'f':
+ fflag = 1;
+ 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) strncpy (Filename, argv[optind], (sizeof Filename)-1);
+ Filename[(sizeof Filename)-1] = '\0';
+
+ } else {
+ usage("file name must be specified for replace");
+ }
+ }
+
+ if (Option == opt_replace) {
+ /* 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)
+ err(ERROR_EXIT, "swapping uids");
+
+ /* 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 if (realpath(Filename, resolved_path) != NULL &&
+ !strcmp(resolved_path, SYSCRONTAB)) {
+ err(ERROR_EXIT, SYSCRONTAB " must be edited manually");
+ } else {
+ if (!(NewCrontab = fopen(Filename, "r")))
+ err(ERROR_EXIT, "%s", Filename);
+ }
+ if (swap_uids_back() < OK)
+ err(ERROR_EXIT, "swapping uids back");
+ }
+
+ Debug(DMISC, ("user=%s, file=%s, option=%s\n",
+ User, Filename, Options[(int)Option]))
+}
+
+static void
+copy_file(FILE *in, FILE *out)
+{
+ int x, ch;
+
+ Set_LineNum(1)
+ /* ignore the top few comments since we probably put them there.
+ */
+ for (x = 0; x < NHEADER_LINES; x++) {
+ ch = get_char(in);
+ if (EOF == ch)
+ break;
+ if ('#' != ch) {
+ putc(ch, out);
+ break;
+ }
+ while (EOF != (ch = get_char(in)))
+ if (ch == '\n')
+ break;
+ if (EOF == ch)
+ break;
+ }
+
+ /* copy the rest of the crontab (if any) to the output file.
+ */
+ if (EOF != ch)
+ while (EOF != (ch = get_char(in)))
+ putc(ch, out);
+}
+
+static void
+list_cmd(void)
+{
+ char n[MAX_FNAME];
+ FILE *f;
+
+ log_it(RealUser, Pid, "LIST", User);
+ (void) snprintf(n, sizeof(n), CRON_TAB(User));
+ if (!(f = fopen(n, "r"))) {
+ if (errno == ENOENT)
+ errx(ERROR_EXIT, "no crontab for %s", User);
+ else
+ err(ERROR_EXIT, "%s", n);
+ }
+
+ /* file is open. copy to stdout, close.
+ */
+ copy_file(f, stdout);
+ fclose(f);
+}
+
+static void
+delete_cmd(void)
+{
+ char n[MAX_FNAME];
+ int ch, first;
+
+ if (!fflag && isatty(STDIN_FILENO)) {
+ (void)fprintf(stderr, "remove crontab for %s? ", User);
+ first = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ if (first != 'y' && first != 'Y')
+ return;
+ }
+
+ log_it(RealUser, Pid, "DELETE", User);
+ if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n))
+ errx(ERROR_EXIT, "path too long");
+ if (unlink(n) != 0) {
+ if (errno == ENOENT)
+ errx(ERROR_EXIT, "no crontab for %s", User);
+ else
+ err(ERROR_EXIT, "%s", n);
+ }
+ poke_daemon();
+}
+
+static void
+check_error(const char *msg)
+{
+ CheckErrorCount++;
+ fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
+}
+
+static void
+edit_cmd(void)
+{
+ char n[MAX_FNAME], q[MAX_TEMPSTR], *editor;
+ FILE *f;
+ int t;
+ struct stat statbuf, fsbuf;
+ WAIT_T waiter;
+ PID_T pid, xpid;
+ mode_t um;
+ int syntax_error = 0;
+ char orig_md5[MD5_SIZE];
+ char new_md5[MD5_SIZE];
+
+ log_it(RealUser, Pid, "BEGIN EDIT", User);
+ if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n))
+ errx(ERROR_EXIT, "path too long");
+ if (!(f = fopen(n, "r"))) {
+ if (errno != ENOENT)
+ err(ERROR_EXIT, "%s", n);
+ warnx("no crontab for %s - using an empty one", User);
+ if (!(f = fopen(_PATH_DEVNULL, "r")))
+ err(ERROR_EXIT, _PATH_DEVNULL);
+ }
+
+ um = umask(077);
+ (void) snprintf(Filename, sizeof(Filename), "/tmp/crontab.XXXXXXXXXX");
+ if ((t = mkstemp(Filename)) == -1) {
+ warn("%s", Filename);
+ (void) umask(um);
+ goto fatal;
+ }
+ (void) umask(um);
+#ifdef HAS_FCHOWN
+ if (fchown(t, getuid(), getgid()) < 0) {
+#else
+ if (chown(Filename, getuid(), getgid()) < 0) {
+#endif
+ warn("fchown");
+ goto fatal;
+ }
+ if (!(NewCrontab = fdopen(t, "r+"))) {
+ warn("fdopen");
+ goto fatal;
+ }
+
+ copy_file(f, NewCrontab);
+ fclose(f);
+ if (fflush(NewCrontab))
+ err(ERROR_EXIT, "%s", Filename);
+ if (fstat(t, &fsbuf) < 0) {
+ warn("unable to fstat temp file");
+ goto fatal;
+ }
+ again:
+ if (swap_uids() < OK)
+ err(ERROR_EXIT, "swapping uids");
+ if (stat(Filename, &statbuf) < 0) {
+ warn("stat");
+ fatal:
+ unlink(Filename);
+ exit(ERROR_EXIT);
+ }
+ if (swap_uids_back() < OK)
+ err(ERROR_EXIT, "swapping uids back");
+ if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
+ errx(ERROR_EXIT, "temp file must be edited in place");
+ if (MD5File(Filename, orig_md5) == NULL) {
+ warn("MD5");
+ goto fatal;
+ }
+
+ if ((editor = getenv("VISUAL")) == NULL &&
+ (editor = getenv("EDITOR")) == NULL) {
+ 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:
+ warn("fork");
+ goto fatal;
+ case 0:
+ /* child */
+ if (setuid(getuid()) < 0)
+ err(ERROR_EXIT, "setuid(getuid())");
+ if (chdir("/tmp") < 0)
+ err(ERROR_EXIT, "chdir(/tmp)");
+ if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR)
+ errx(ERROR_EXIT, "editor or filename too long");
+ execlp(editor, editor, Filename, (char *)NULL);
+ err(ERROR_EXIT, "%s", editor);
+ /*NOTREACHED*/
+ default:
+ /* parent */
+ break;
+ }
+
+ /* parent */
+ {
+ void (*sig[3])(int signal);
+ sig[0] = signal(SIGHUP, SIG_IGN);
+ sig[1] = signal(SIGINT, SIG_IGN);
+ sig[2] = signal(SIGTERM, SIG_IGN);
+ xpid = wait(&waiter);
+ signal(SIGHUP, sig[0]);
+ signal(SIGINT, sig[1]);
+ signal(SIGTERM, sig[2]);
+ }
+ if (xpid != pid) {
+ warnx("wrong PID (%d != %d) from \"%s\"", xpid, pid, editor);
+ goto fatal;
+ }
+ if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
+ warnx("\"%s\" exited with status %d", editor, WEXITSTATUS(waiter));
+ goto fatal;
+ }
+ if (WIFSIGNALED(waiter)) {
+ warnx("\"%s\" killed; signal %d (%score dumped)",
+ editor, WTERMSIG(waiter), WCOREDUMP(waiter) ?"" :"no ");
+ goto fatal;
+ }
+ if (swap_uids() < OK)
+ err(ERROR_EXIT, "swapping uids");
+ if (stat(Filename, &statbuf) < 0) {
+ warn("stat");
+ goto fatal;
+ }
+ if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
+ errx(ERROR_EXIT, "temp file must be edited in place");
+ if (MD5File(Filename, new_md5) == NULL) {
+ warn("MD5");
+ goto fatal;
+ }
+ if (swap_uids_back() < OK)
+ err(ERROR_EXIT, "swapping uids back");
+ if (strcmp(orig_md5, new_md5) == 0 && !syntax_error) {
+ warnx("no changes made to crontab");
+ goto remove;
+ }
+ warnx("installing new crontab");
+ switch (replace_cmd()) {
+ case 0: /* Success */
+ break;
+ case -1: /* Syntax error */
+ 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':
+ syntax_error = 1;
+ goto again;
+ case 'n':
+ goto abandon;
+ default:
+ fprintf(stderr, "Enter Y or N\n");
+ }
+ }
+ /*NOTREACHED*/
+ case -2: /* Install error */
+ abandon:
+ warnx("edits left in %s", Filename);
+ goto done;
+ default:
+ warnx("panic: bad switch() in replace_cmd()");
+ 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(void)
+{
+ 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();
+
+ if (envp == NULL) {
+ warnx("cannot allocate memory");
+ return (-2);
+ }
+
+ (void) snprintf(n, sizeof(n), "tmp.%d", Pid);
+ if (snprintf(tn, sizeof(tn), CRON_TAB(n)) >= (int)sizeof(tn)) {
+ warnx("path too long");
+ return (-2);
+ }
+
+ if (!(tmp = fopen(tn, "w+"))) {
+ warn("%s", 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), ftello(tmp));
+ fflush(tmp); rewind(tmp);
+
+ if (ferror(tmp)) {
+ warnx("error while writing new crontab to %s", 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_entry(e);
+ break;
+ case TRUE:
+ break;
+ }
+ }
+
+ if (CheckErrorCount != 0) {
+ warnx("errors in crontab file, can't install");
+ 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
+ {
+ warn("chown");
+ fclose(tmp); unlink(tn);
+ return (-2);
+ }
+
+#ifdef HAS_FCHMOD
+ if (fchmod(fileno(tmp), 0600) < OK)
+#else
+ if (chmod(tn, 0600) < OK)
+#endif
+ {
+ warn("chown");
+ fclose(tmp); unlink(tn);
+ return (-2);
+ }
+
+ if (fclose(tmp) == EOF) {
+ warn("fclose");
+ unlink(tn);
+ return (-2);
+ }
+
+ if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n)) {
+ warnx("path too long");
+ unlink(tn);
+ return (-2);
+ }
+
+ if (rename(tn, n)) {
+ warn("error renaming %s to %s", tn, n);
+ unlink(tn);
+ return (-2);
+ }
+
+ log_it(RealUser, Pid, "REPLACE", User);
+
+ /*
+ * Creating the 'tn' temp file has already updated the
+ * modification time of the spool directory. Sleep for a
+ * second to ensure that poke_daemon() sets a later
+ * modification time. Otherwise, this can race with the cron
+ * daemon scanning for updated crontabs.
+ */
+ sleep(1);
+
+ poke_daemon();
+
+ return (0);
+}
+
+static void
+poke_daemon(void)
+{
+ if (utime(SPOOL_DIR, NULL) < OK) {
+ warn("can't update mtime on spooldir %s", SPOOL_DIR);
+ return;
+ }
+}
diff --git a/usr.sbin/cron/doc/CHANGES b/usr.sbin/cron/doc/CHANGES
new file mode 100644
index 000000000000..84146a59b2be
--- /dev/null
+++ b/usr.sbin/cron/doc/CHANGES
@@ -0,0 +1,157 @@
+--------
+
+Vixie Cron Changes from V2 to V3
+Paul Vixie
+29-Dec-1993
+
+The crontab command now conforms to POSIX 1003.2. This means that when you
+install it, if you have any "crontab" command lines floating around in shell
+scripts (such as /etc/rc or /etc/rc.local), you will need to change them.
+
+I have integrated several changes made by BSDi for their BSD/386 operating
+system; these were offerred to me before I started consulting for them, so
+it is safe to say that they were intended for publication. Most notably,
+the name of the cron daemon has changed from "crond" to "cron". This was
+done for compatibility with 4.3BSD. Another change made for the same reason
+is the ability to read in an /etc/crontab file which has an extra field in
+each entry, between the time fields and the command. This field is a user
+name, and it permits the /etc/crontab command to contain commands which are
+to be run by any user on the system. /etc/crontab is not "installed" via
+the crontab(1) command; it is automatically read at startup time and it will
+be reread whenever it changes.
+
+I also added a "-e" option to crontab(1). Nine people also sent me diffs
+to add this option, but I had already implemented it on my own. I actually
+released an interim version (V2.2, I think) for limited testing, and got a
+chance to fix a bad security bug in the "-e" option thanks to XXX.
+
+The daemon used to be extraordinarily sloppy in its use of file descriptors.
+A heck of a lot of them were left open in spawned jobs, which caused problems
+for the daemon and also caused problems with the spawned jobs if they were
+shell scripts since "sh" and "csh" have traditionally used hidden file
+descriptors to pass information to subshells, and cron was causing them to
+think they were subshells. If you had trouble with "sh" or "csh" scripts in
+V2, chances are good that V3 will fix your problems.
+
+About a dozen people have reminded me that I forgot to initialize
+"crontab_fd" in database.c. Keith Cantrell was the first, so he gets the
+point.
+
+Steve Simmons reminded me that once an account has been deleted from the
+system, "crontab -u USER -d" will not work. My solution is to suggest to
+all of you that before you delete a user's account, you first delete that
+user's crontab file if any. From cron's point of view, usernames can never
+be treated as arbitrary strings. Either they are valid user names, or they
+are not. I will not make an exception for the "-d" case, for security
+reasons that I consider reasonable. It is trivial for a root user to delete
+the entry by hand if necessary.
+
+Dan O'Neil reminded me that I forgot to reset "log_fd" in misc.c. A lot of
+others also reminded me of this, but Dan gets the point. I didn't fix it
+there, since the real bug was that it should have been open in the parent.
+
+Peter Kabal reminded me that I forgot to "#ifdef DEBUGGING" some code in
+misc.c. Hans Trompert actually told me first, but Peter sent the patch so
+he gets the point.
+
+Russell Nelson told me that I'd forgotten to "#include <syslog.h>" in misc.c,
+which explains why a lot of other people complained that it wasn't using
+syslog even when they configured it that way :-). Steve Simmons told me
+first, though, so he gets the point.
+
+An interim version of the daemon tried to "stat" every file before
+executing it; this turned out to be a horribly bad idea since finding the
+name of a file from a shell command is a hard job (that's why we have
+shells, right?) I removed this bogus code. Dave Burgess gets the point.
+
+Dennis R. Conley sent a suggestion for MMDF systems, which I've added to the
+comments in cron.h.
+
+Mike Heisler noted that I use comments in the CONVERSION file which are
+documented as illegal in the man pages. Thanks, Mike.
+
+Irving Wolfe sent me some very cheerful changes for a NeXT system, but I
+consider the system itself broken and I can't bring myself to #ifdef for
+something as screwed up as this system seems to be. However, various others
+did send me smaller patches which appear to have cause cron to build and run
+correctly on (the latest) NeXT machines, with or without the "-posix" CFLAG.
+Irving also asked for a per-job MAILTO, and this was finally added later when
+I integrated the BSD/386 changes contributed by BSDi, and generalized some of
+the parsing.
+
+Lots of folks complained that the autogenerated "Date:" header wasn't in
+ARPA format. I didn't understand this -- either folks will use Sendmail and
+not generate a Date: at all (since Sendmail will do it), or folks will use
+something other than Sendmail which won't care about Date: formats. But
+I've "fixed" it anyway...
+
+Several people suggested that "*" should be able to take a "/step". One person
+suggested that "N/step" ought to mean "N-last/step", but that's stretching things
+a bit far. "*/step" seems quite intuitive to me, so I've added it. Colin Plumb
+sent in the first and most polite request for this feature.
+
+As with every release of Cron, BIND, and seemingly everything else I do, one
+user stands out with the most critical but also the most useful analysis.
+Cron V3's high score belongs to Peter Holzer, who sent in the nicest looking
+patch for the "%" interpretation problem and also helped me understand a
+tricky bit of badness in the "log_fd" problem.
+
+agulbra@flode.nvg.unit.no wins the honors for being the first to point out the
+nasty security hole in "crontab -r". 'Nuff said.
+
+Several folks pointed out that log_it() needed to exist even if logging was
+disabled. Some day I will create a tool that will compile a subsystem with
+every possible combination and permutation of #ifdef options, but meanwhile
+thanks to everybody.
+
+job_runqueue() was using storage after freeing it, since Jordan told me back
+in 1983 that C let you do that, and I believed him in 1986 when I wrote all
+this junk. Linux was the first to die from this error, and the Linux people
+sent me the most amazing, um, collection of patches for this problem. Thanks
+for all the fish.
+
+Jeremy Bettis reminded me that popen() isn't safe. I grabbed Ken Arnold's
+version of popen/pclose from the ftpd and hacked it to taste. We're safe now,
+from this at least.
+
+Branko Lankester sent me a very timely and helpful fix for a looming security
+problem in my "crontab -e" implementation.
+
+--------
+
+Vixie Cron Changes from V1 to V2
+Paul Vixie
+8-Feb-1988
+
+Many changes were made in a rash of activity about six months ago, the exact
+list of which is no longer clear in my memory. I know that V1 used a file
+called POKECRON in /usr/spool/cron to tell it that it was time to re-read
+all the crontab files; V2 uses the modtime the crontab directory as a flag to
+check out the crontab files; those whose modtime has changed will be re-read,
+and the others left alone. Note that the crontab(1) command will do a utimes
+call to make sure the mtime of the dir changes, since the filename/inode will
+often remain the same after a replacement and the mtime wouldn't change in
+that case.
+
+8-Feb-88: made it possible to use much larger environment variable strings.
+ V1 allowed 100 characters; V2 allows 1000. This was needed for PATH
+ variables on some systems. Thanks to Toerless Eckert for this idea.
+ E-mail: UUCP: ...pyramid!fauern!faui10!eckert
+
+16-Feb-88: added allow/deny, moved /usr/spool/cron/crontabs to
+ /usr/lib/cron/tabs. allow and deny are /usr/lib/cron/{allow,deny},
+ since the sysv naming for this depends on 'at' using the same
+ dir, which would be stupid (hint: use /usr/{lib,spool}/at).
+
+22-Feb-88: made it read the spool directory for crontabs and look each one
+ up using getpwnam() rather than reading all passwds with getpwent()
+ and trying to open each crontab.
+
+9-Dec-88: made it sync to :00 after the minute, makes cron predictable.
+ added logging to /var/cron/log.
+
+14-Apr-90: (actually, changes since December 1989)
+ fixed a number of bugs reported from the net and from John Gilmore.
+ added syslog per Keith Bostic. security features including not
+ being willing to run a command owned or writable by other than
+ the owner of the crontab 9not working well yet)
diff --git a/usr.sbin/cron/doc/CONVERSION b/usr.sbin/cron/doc/CONVERSION
new file mode 100644
index 000000000000..6067650fa683
--- /dev/null
+++ b/usr.sbin/cron/doc/CONVERSION
@@ -0,0 +1,84 @@
+
+Conversion of BSD 4.[23] crontab files:
+
+Edit your current crontab (/usr/lib/crontab) into little pieces, with each
+users' commands in a different file. This is different on 4.2 and 4.3,
+but I'll get to that below. The biggest feature of this cron is that you
+can move 'news' and 'uucp' cron commands into files owned and maintainable
+by those two users. You also get to rip all the fancy 'su' footwork out
+of the cron commands. On 4.3, there's no need for the 'su' stuff since the
+user name appears on each command -- but I'd still rather have separate
+crontabs with separate environments and so on.
+
+Leave the original /usr/lib/crontab! This cron doesn't use it, so you may
+as well keep it around for a while in case something goes wakko with this
+fancy version.
+
+Most commands in most crontabs are run by root, have to run by root, and
+should continue to be run by root. They still have to be in their own file;
+I recommend /etc/crontab.src or /usr/adm/crontab.src.
+
+'uucp's commands need their own file; how about /usr/lib/uucp/crontab.src?
+'news' also, perhaps in /usr/lib/news/crontab.src...
+
+I say `how about' and `perhaps' because it really doesn't matter to anyone
+(except you) where you put the crontab source files. The `crontab' command
+COPIES them into a protected directory (CRONDIR/SPOOL_DIR in cron.h), named
+after the user whose crontab it is. If you want to examine, replace, or
+delete a crontab, the `crontab' command does all of those things. The
+various `crontab.src' (my suggested name for them) files are just source
+files---they have to be copied to SPOOLDIR using `crontab' before they'll be
+executed.
+
+On 4.2, your crontab might have a few lines like this:
+
+ 5 * * * * su uucp < /usr/lib/uucp/uudemon.hr
+ 10 4 * * * su uucp < /usr/lib/uucp/uudemon.day
+ 15 5 * * 0 su uucp < /usr/lib/uucp/uudemon.wk
+
+...or like this:
+
+ 5 * * * * echo /usr/lib/uucp/uudemon.hr | su uucp
+ 10 4 * * * echo /usr/lib/uucp/uudemon.day | su uucp
+ 15 5 * * 0 echo /usr/lib/uucp/uudemon.wk | su uucp
+
+On 4.3, they'd look a little bit better, but not much:
+
+ 5 * * * * uucp /usr/lib/uucp/uudemon.hr
+ 10 4 * * * uucp /usr/lib/uucp/uudemon.day
+ 15 5 * * 0 uucp /usr/lib/uucp/uudemon.wk
+
+For this cron, you'd create /usr/lib/uucp/crontab.src (or wherever you want
+to keep uucp's commands) which would look like this:
+
+ # /usr/lib/uucp/crontab.src - uucp's crontab
+ #
+ PATH=/usr/lib/uucp:/bin:/usr/bin
+ SHELL=/bin/sh
+ HOME=/usr/lib/uucp
+ #
+ 5 * * * * uudemon.hr
+ 10 4 * * * uudemon.day
+ 15 5 * * 0 uudemon.wk
+
+The application to the `news' cron commands (if any) is left for you to
+figure out. Likewise if there are any other cruddy-looking 'su' commands in
+your crontab commands, you don't need them anymore: just find a good place
+to put the `crontab.src' (or whatever you want to call it) file for that
+user, put the cron commands into it, and install it using the `crontab'
+command (probably with "-u USERNAME", but see the man page).
+
+If you run a 4.2-derived cron, you could of course just install your current
+crontab in toto as root's crontab. It would work exactly the way your
+current one does, barring the extra steps in installing or changing it.
+There would still be advantages to this cron, mostly that you get mail if
+there is any output from your cron commands.
+
+One note about getting mail from cron: you will probably find, after you
+install this version of cron, that your cron commands are generating a lot
+of irritating output. The work-around for this is to redirect all EXPECTED
+output to a per-execution log file, which you can examine if you want to
+see the output from the "last time" a command was executed; if you get any
+UNEXPECTED output, it will be mailed to you. This takes a while to get
+right, but it's amazingly convenient. Trust me.
+
diff --git a/usr.sbin/cron/doc/FEATURES b/usr.sbin/cron/doc/FEATURES
new file mode 100644
index 000000000000..71b1486a8e01
--- /dev/null
+++ b/usr.sbin/cron/doc/FEATURES
@@ -0,0 +1,83 @@
+
+Features of Vixie's cron relative to BSD 4.[23] and SysV crons:
+
+-- Environment variables can be set in each crontab. SHELL, USER,
+ LOGNAME, and HOME are set from the user's passwd entry; all except
+ USER can be changed in the crontab. PATH is especially useful to
+ set there. TZ can be set, but cron ignores it other than passing
+ it on through to the commands it runs. Format is
+
+ variable=value
+
+ Blanks surrounding the '=' will be eaten; other blanks in value are
+ okay. Leading or trailing blanks can be preserved by quoting, single
+ or double quotes are okay, just so they match.
+
+ PATH=.:/bin:/usr/bin
+ SHELL=/bin/sh
+ FOOBAR = this is a long blanky example
+
+ Above, FOOBAR would get "this is a long blanky example" as its value.
+
+ SHELL and HOME will be used when it's time to run a command; if
+ you don't set them, HOME defaults to your /etc/passwd entry
+ and SHELL defaults to /bin/sh.
+
+ MAILTO, if set to the login name of a user on your system, will be the
+ person that cron mails the output of commands in that crontab. This is
+ useful if you decide on BINMAIL when configuring cron.h, since binmail
+ doesn't know anything about aliasing.
+
+-- Weekdays can be specified by name. Case is not significant, but only
+ the first three letters should be specified.
+
+-- Months can likewise be specified by name. Three letters only.
+
+-- Ranges and lists can be mixed. Standard crons won't allow '1,3-5'.
+
+-- Ranges can specify 'step' values. '10-16/2' is like '10,12,14,16'.
+
+-- Sunday is both day 0 and day 7 -- apparently BSD and ATT disagree
+ about this.
+
+-- Each user gets their own crontab file. This is a win over BSD 4.2,
+ where only root has one, and over BSD 4.3, where they made the crontab
+ format incompatible and although the commands can be run by non-root
+ uid's, root is still the only one who can edit the crontab file. This
+ feature mimics the SysV cron.
+
+-- The 'crontab' command is loosely compatible with SysV, but has more
+ options which just generally make more sense. Running crontab with
+ no arguments will print a cute little summary of the command syntax.
+
+-- Comments and blank lines are allowed in the crontab file. Comments
+ must be on a line by themselves; leading whitespace is ignored, and
+ a '#' introduces the comment.
+
+-- (big win) If the `crontab' command changes anything in any crontab,
+ the 'cron' daemon will reload all the tables before running the
+ next iteration. In some crons, you have to kill and restart the
+ daemon whenever you change a crontab. In other crons, the crontab
+ file is reread and reparsed every minute even if it didn't change.
+
+-- In order to support the automatic reload, the crontab files are not
+ readable or writable except by 'crontab' or 'cron'. This is not a
+ problem, since 'crontab' will let you do pretty much whatever you
+ want to your own crontab, or if you are root, to anybody's crontab.
+
+-- If any output is generated by a command (on stdout OR stderr), it will
+ be mailed to the owner of the crontab that contained the command (or
+ MAILTO, see discussion of environment variables, above). The headers
+ of the mail message will include the command that was run, and a
+ complete list of the environment that was passed to it, which will
+ contain (at least) the USER (LOGNAME on SysV), HOME, and SHELL.
+
+-- 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'. I didn't think up
+ this behaviour; it's how cron has always worked but the documentation
+ hasn't been very clear. I have been told that some AT&T crons do not
+ act this way and do the more reasonable thing, which is (IMHO) to "or"
+ the various field-matches together. In that sense this cron may not
+ be completely similar to some AT&T crons.
diff --git a/usr.sbin/cron/doc/INSTALL b/usr.sbin/cron/doc/INSTALL
new file mode 100644
index 000000000000..326be482dd71
--- /dev/null
+++ b/usr.sbin/cron/doc/INSTALL
@@ -0,0 +1,91 @@
+/* Copyright 1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+$Id: INSTALL,v 1.2 1998/08/14 00:32:35 vixie Exp $
+
+Read the comments at the top of the Makefile, then edit the area marked
+'configurable stuff'.
+
+Edit config.h. The stuff I expect you to change is down a bit from the
+top of the file, but it's clearly marked. Also look at pathnames.h.
+
+You don't have to create the /var/cron or /var/cron/tabs directories, since
+both the daemon and the `crontab' program will do this the first time they
+run if they don't exist. You do need to have a /var, though -- just "mkdir
+/var" if you don't have one, or you can "mkdir /usr/var; ln -s /usr/var /var"
+if you expect your /var to have a lot of stuff in it.
+
+You will also need /usr/local/etc and /usr/local/bin directories unless you
+change the Makefile. These will have to be created by hand, but if you are
+a long-time Usenet user you probably have them already. /usr/local/man is
+where I keep my man pages, but I have the source for `man' and you probably
+do not. Therefore you may have to put the man pages into /usr/man/manl,
+which will be hard since there will be name collisions. (Note that the man
+command was originally written by Bill Joy before he left Berkeley, and it
+contains no AT&T code, so it is in UUNET's archive of freely-distributable
+BSD code.)
+
+LINUX note: /usr/include/paths.h on some linux systems shows _PATH_SENDMAIL
+ to be /usr/bin/sendmail even though sendmail is installed in /usr/lib.
+ you should check this out.
+
+say:
+ make all
+
+su and say:
+ make install
+
+Note that if I can get you to "su and say" something just by asking, you have
+a very serious security problem on your system and you should look into it.
+
+Edit your /usr/lib/crontab file into little pieces -- see the CONVERSION file
+for help on this.
+
+Use the `crontab' command to install all the little pieces you just created.
+Some examples (see below before trying any of these!)
+
+ crontab -u uucp -r /usr/lib/uucp/crontab.src
+ crontab -u news -r /usr/lib/news/crontab.src
+ crontab -u root -r /usr/adm/crontab.src
+
+Notes on above examples: (1) the .src files are copied at the time the
+command is issued; changing the source files later will have no effect until
+they are reinstalled with another `crontab -r' command. (2) The crontab
+command will affect the crontab of the person using the command unless `-u
+USER' is given; `-u' only works for root. When using most `su' commands
+under most BSD's, `crontab' will still think of you as yourself even though
+you may think of yourself as root -- so use `-u' liberally. (3) the `-r'
+option stands for `replace'; check the man page for crontab(1) for other
+possibilities.
+
+Kill your existing cron daemon -- do `ps aux' and look for /etc/cron.
+
+Edit your /etc/rc or /etc/rc.local, looking for the line that starts up
+/etc/cron. Comment it out and add a line to start the new cron daemon
+-- usually /usr/local/etc/cron, unless you changed it in the Makefile.
+
+Start up this cron daemon yourself as root. Just type /usr/local/etc/cron
+(or whatever); no '&' is needed since the daemon forks itself and the
+process you executed returns immediately.
+
+ATT notes: for those people unfortunate enough to be stuck on a AT&T UNIX,
+you will need the public-domain "libndir", found in the B News source and in
+any comp.sources.unix archive. You will also need to hack the code some.
diff --git a/usr.sbin/cron/doc/MAIL b/usr.sbin/cron/doc/MAIL
new file mode 100644
index 000000000000..149e5527171c
--- /dev/null
+++ b/usr.sbin/cron/doc/MAIL
@@ -0,0 +1,473 @@
+[ this is really old mail that came to me in response to my 1986 posting
+ to usenet asking for feature suggestions before releasing the first
+ version of cron. it is presented here for its entertainment value.
+ --vix ]
+
+From ptsfa!lll-crg!ames!acornrc!bob Wed Dec 31 10:07:08 1986
+Date: Wed, 31 Dec 86 08:59:31 pst
+From: lll-crg!ames!acornrc!bob (Bob Weissman)
+To: ptsfa!vixie!paul
+Status: RO
+
+Sure, here's a suggestion: I'd like to be able to run a program, say,
+every two hours. Current cron requires me to write
+0,2,4,6,8,10,12,14,16,18,20,22 in the hours field. How about a notation
+to handle this more elegantly?
+
+<< Okay, I've allowed 0-22/2 as a means of handling this.
+ The time specification for my cron is as follows:
+ specification = range {"," range}
+ range = (start "-" finish ["/" step]) | single-unit
+ This allows "1,3,5-7", which the current cron doesn't (it won't
+ do a range inside a list), and handles your specific need. >>
+
+From drw@mit-eddie Wed Dec 31 18:25:27 1986
+Date: Wed, 31 Dec 86 14:28:19 est
+From: drw@mit-eddie (Dale Worley)
+To: mit-eddie!vixie!paul
+Status: RO
+
+We have a lot of lines in our crontab of the form
+
+ 00 12 * * * su user < /usr/users/user/script.file
+
+This barfs (silently!) on our system (Dec Ultrix 1.2 == 4.2bsd) if
+user's shell is csh. This, I am told, is because csh requires that
+the environment be set up in certain ways, which cron doesn't do.
+(Actually, I believe, it is because /etc/rc, which runs cron, doesn't
+set up the environment enough for csh to run, and cron just inherits
+the situation.) Anyway, the point is that if you find out what csh
+really needs in its environment, you might want to set up cron to
+provide some reasonable defaults (if it isn't supplied by cron's
+parent). Also, could you tell me what csh needs, if you find out, so
+we can hack our /etc/rc?
+
+<< well, the environment IS a problem. processes that cron forks
+ will inherit the environment of the person who ran the cron
+ daemon... I plan to edit out such useless things as TERMCAP,
+ TERM, and the like; supply correct values for HOME, USER, CWD,
+ and whatever else comes to mind. I'll make sure csh works... >>
+From ptsfa!ames!seismo!dgis!generous Thu Jan 1 07:33:17 1987
+Date: Thu Jan 1 10:29:20 1987
+From: ames!seismo!dgis!generous (Curtis Generous)
+To: nike!ptsfa!vixie!paul
+Status: RO
+
+Paul:
+
+One of the limitations of the present versions of cron is the lack
+of the capability of specifying a way to execute a command every
+n units of time.
+
+Here is a good example:
+
+# Present method to start up uucico
+02,12,22,32,42,52 * * * * exec /usr/lib/uucp/uucico -r1
+
+# New method ?? (the ':' here is just one possibility for syntax)
+02:10 * * * * exec /usr/lib/uucp/uucico -r1
+
+This method would prove very helpful for those programs that get started
+every few minutes, making the entry long and not easily readable. The first
+number would specify the base time, and the second number the repetition
+interval.
+
+<< Good idea, but bob@acornrc beat you to it. I used '/' instead of
+ ':'. This is my personal preference, and seems intuitive when you
+ think of the divide operator in C... Does anyone have a preference? >>
+
+From ptsfa!lll-lcc!seismo!decuac!c3pe!c3engr!charles Thu Jan 1 17:04:24 1987
+From: lll-lcc!seismo!c3pe!c3engr!charles (Charles Green)
+To: c3pe!decuac!dolqci!vrdxhq!seismo!lll-lcc!ptsfa!vixie!paul
+Date: Thu Jan 1 19:22:47 1987
+Status: RO
+
+Well, this isn't a compatible extension, but I have in times past wondered
+about a facility to let you start a process at intervals of, say, 17 minutes,
+instead of particular minutes out of each hour.
+
+<< This was a popular request! >>
+
+From seismo!uwvax!astroatc!nicmad!norvax!mann Sun Jan 4 13:04:01 1987
+Date: Fri, 2 Jan 87 09:23:53 cst
+From: lll-lcc!seismo!uwvax!astroatc!nicmad!norvax!mann (Tom Mann)
+To: ptsfa!vixie!paul
+Status: RO
+
+I'm not sure if it is in cron (either SysV or BSD ... if it is, I haven't
+figured it out ) but a comment feature would SURE BE NICE!.
+There are times when I want to comment out an entry
+for a period of time; it might also make it a lot more legible.
+
+<< My cron allows blank lines and standard #-type comments. I know
+ that one BSD4.2 cron I've used had it. I don't know about SysV. >>
+
+From ptsfa!hoptoad!hugh Mon Jan 5 10:26:46 1987
+Date: Mon, 5 Jan 87 01:22:17 PST
+From: hoptoad!hugh (Hugh Daniel)
+To: ptsfa!vixie!paul
+Status: RO
+
+ Hi, I do have a BIG one that I would like. I want to log ALL output
+from command lines into a file for each line. Thus I might have a chance
+of finding out why my crontab entry did not work.
+ This would seem to work best if done by cron, as it is now I have a google
+of shell scripts laying about just to put the error output where I can see
+it.
+
+<< My cron (and the SysV cron) will send mail to the owner of the
+ particular crontab file if a command generates any output on stdout
+ or stderr. This can be irritating, but if you write a script such
+ that any output means a problem occurred, you can avoid most logfile
+ needs, and not generate mail except in unforeseen circumstances. >>
+
+From ptsfa!dual!ucbvax!ihnp4!anvil!es!Robert_Toxen Mon Jan 5 13:08:46 1987
+From: dual!ucbvax!ihnp4!anvil!es!Robert_Toxen
+Date: Fri, 2 Jan 87 14:25:29 EST
+To: anvil!ihnp4!ucbvax!dual!ptsfa!vixie!paul
+Status: RO
+
+Here are some suggestions:
+1. Run it through the C preprocessor via "/lib/<whatever>".
+
+<< hmmm. this seems of limited utility, and if you really wanted
+ to do it that way, you could do it yourself (since users can
+ write to their own crontab files). I'll add '-' (read stdin)
+ to the crontab installer program to facilitate this. >>
+
+2. Allow specifying every Nth day of week, i.e., every second Wednesday.
+ I did this to calendar by separating the day of week (Wed=4, which one
+ to start on and N with slashes). I took modulo the day of year as a
+ starting point so that someone with a desk calendar documenting such
+ things can easily determine the offset (second number). I did this
+ while at SGI; alas I don't have a copy of the code.
+
+<< I can see how this could be useful, but I'm not sure how I'd
+ implement it. Cron currently doesn't keep track of the last time
+ a given command was run; whether the current Wednesday is the first
+ or second since the command was last run would be pretty hard to
+ figure out. I'd have to keep a database of commands and their
+ execution around, and purge it when the crontab was overwritten.
+ This is too much work for me, but if someone adds it, let me know. >>
+
+From ptsfa!ames!seismo!cbmvax!devon!paul Tue Jan 6 05:50:17 1987
+From: ames!seismo!cbmvax!devon!paul
+To: cbmvax!seismo!nike!ptsfa!vixie!paul
+Date: Mon Jan 5 09:29:57 1987
+Status: RO
+
+One problem that has always plagued me with cron is the assumed ORing.
+I'd like to see some type of ANDing implemented. I guess I can best
+describe this by example. Say I have the following line in my crontab
+file:
+
+* * 4-31 * 1-6 /usr/bin/command
+
+What this does is run 'command' on the 4th thru 31st days of the
+month, AND on Monday thru Saturday; which probably means running it
+every day of the month (unless Sunday falls on days 1-3). This
+happens because cron runs the command if the day-of-month OR the
+day-of-week is true.
+
+What I'd like to happen with the above line is to run the command ONLY
+on Monday thru Saturday any time after the 3rd of the month, e.g. if
+the day-of-month AND the day-of-week are true.
+
+My proposal to you is to implement some special chars for the first
+five fields. Examples:
+
+* * !1-3 * 1-6 /usr/bin/command
+
+(run command Mon-Sat, but NOT [!] on the first 3 days of the month)
+
+* * &4-31 * &1-6 /usr/bin/command
+
+(run command if day-of-month AND day-of-week are true)
+
+Get the picture? This would be compatible with existing versions of
+cron (which wouldn't currently be using any special characters, so
+that old crontabs would be handled correctly).
+
+<< This message made me aware of the actual boolean expression involved
+ in a crontab entry. I'd assumed that it was
+ (minute && hour && DoM && month && DoW)
+ But it's really
+ (minute && hour && month && (DoM || DoW))
+
+ I can see some value in changing this, but with a fixed order of
+ fields, operators get to be kindof unary, which && and || really
+ aren't. If someone has an idea on a syntax that allows useful
+ variations to the standard (&& && && (||)) default, please suggest. >>
+
+From bobkat!pedz Tue Jan 6 20:02:10 1987
+From: pedz@bobkat.UUCP (Pedz Thing)
+Date: 2 Jan 87 17:34:44 GMT
+Status: RO
+
+Log files! It would be nice to be able to specify a log for cron
+itself and also a log for each program's stdout and stderr to go to.
+The latter can of course be done with > and 2> but it would be nice if
+there could be a single line with some sort of pattern like
+`> /usr/spool/log/%' and the command would be substituted for the %.
+Another thing which would be nice is to be able to specify which shell
+to call to give the command to.
+
+<< Log files are done with mail. The '%' idea could be useful if
+ a different character were used (% is special to cron, see man
+ page); a different directory would have to be chosen, since each
+ user has their own crontab file; and something intelligent would
+ have to be done in the file naming, since the first word of the
+ command might be ambiguous (with other commands). In short, it's
+ too much work. Sorry. >>
+
+From guy%gorodish@sun Tue Jan 6 20:03:13 1987
+From: guy%gorodish@sun (Guy Harris)
+Message-ID: <10944@sun.uucp>
+Date: 5 Jan 87 12:09:09 GMT
+References: <429@vixie.UUCP> <359@bobkat.UUCP>
+Sender: news@sun.uucp
+Status: RO
+
+> Another thing which would be nice is to be able to specify which shell
+> to call to give the command to.
+
+Well, the obvious choice would be the user's shell, but this wouldn't work
+for accounts like "uucico".
+
+<< I use the owning user's shell, and to handle "uucico" I check a
+ list of "acceptable shells" (currently compiled in, does anybody
+ mind?), substituting a default (compiled in) shell if the user's
+ shell isn't on the list.
+
+ BTW, "compiled in" means that it's in a .h file, easily changed
+ during installation, but requiring recompilation to modify. You
+ don't have to go digging through the code to find it... >>
+
+From qantel!hplabs!ucbvax!mwm@violet.berkeley.edu Tue Jan 6 21:24:48 1987
+To: hplabs!qantel!vixie!paul (Paul Vixie Esq)
+Date: 04 Jan 87 00:42:35 PST (Sun)
+From: Mike Meyer <mwm@violet.berkeley.edu>
+Status: RO
+
+<<[Discussion of RMS/FSF, and mwm's GNU Cron deleted]>>
+
+Oh, yeah - here are the extensions on my cron:
+
+1) Sunday is both day 0 and day 7, so it complies with both SysV and
+BSD cron.
+
+<< Good idea. I did it too, thanks for informing me. >>
+
+2) At is integrated into the cron. Instead of atrun to scan the
+/usr/spool/at directory, at files are put into the /usr/lib/cron
+directory along with users cron files, and cron fabricates a line from
+a crontab file to run them. This is considered a major win by all who
+use it.
+
+<< I don't use 'at', and my cron doesn't do anything with it. To run
+ 'at', I use 'atrun' the same way the current BSD cron does. My
+ crontab files are in /usr/spool/cron/crontabs, in the SysV
+ tradition -- not in /usr/lib/cron. This is a configuration
+ parameter, of course. >>
+
+There are two known restrictions:
+
+1) I don't support any of the SysV security hooks. I don't have a use
+for them, and RMS didn't like the idea at all :-).
+
+<< This means cron.allow and cron.deny. I plan to support them, as
+ they've been quite helpful at various HPUX sites I've administered. >>
+
+2) Cron expects to be able to create files with names longer than 14
+characters, which makes it hard to run on SysV. At least one person
+was working on a port, but I don't know how it's going. That might
+make for a good reason for releasing yours, right there.
+
+<< If someone has SysV (with the 14-character limit), they probably
+ won't want my cron, since it doesn't add much to the standard
+ version (which they may have support for). My cron is not currently
+ portable to non-BSD systems, since it relies on interval timers (I
+ needed to sleep for intervals more granular than seconds alone would
+ allow). The port would be trivial, and I will do it if a lot of
+ people ask for it... >>
+
+Oh, yeah - I'm going to see about getting this cron integrated into
+the next 4BSD release.
+
+<< How does one go about this? I have a few nifty gadgets I'd like
+ to contribute, this cron being one of them... >>
+
+<<[more FSF/GNU discussion deleted]>>
+
+From qantel!hplabs!ames!ut-sally!ut-ngp!melpad!bigtex!james Tue Jan 6 21:24:57 1987
+Posted-Date: Fri, 2 Jan 87 19:26:16 est
+Date: Fri, 2 Jan 87 19:26:16 est
+From: hplabs!ames!ut-sally!ut-ngp!bigtex!james
+To: vixie!paul
+Status: RO
+
+Yes!!! There are several critical failures in System V cron...
+
+1. Pass all variables in cron's environment into the environment of things
+ cron starts up, or at least into the crontab entries started up (at jobs
+ will inherit the environment of the user). If nothing else it is critically
+ important that the TZ variable be passed on. PATH should be passed on too.
+ Basically, passing environment values allows one to design a standard
+ environment with TZ and PATH and have that run by everything. If anyone
+ tells you this is no big deal, consider what happens when uucico is
+ started by cron in CA to make a long distance phone link... Unless the
+ administrator is really on his/her toes, calls scheduled at 5pm will really
+ go at two in the afternoon, needlessly incurring huge phone bills, all
+ because System V refuses to pass the TZ from its environment down. There
+ are work arounds, but only putting it in cron will really work. This is
+ not a security hole.
+
+<< delete TERM and TERMCAP; modify HOME, USER, and CWD; pass TZ and
+ PATH through undisturbed. any other requests out there?
+
+ BSD doesn't have this problem -- TZ is passed right on through if
+ you define it in the shell before starting my cron daemon. However,
+ the BSD I'm running this on doesn't need TZ to be defined anyway...
+ The default in the kernel has been just fine so far... But just the
+ same, if/when I port to SysV (I guess I really should), I'll make
+ sure this works right.
+
+ I guess I've been spoiled. HPUX is SysV-based, and I never had a
+ problem with cron and TZ when I used it. >>
+
+2. A way to avoid logging stuff in /usr/lib/cron/log. I have a cron entry
+ run uudemon.hr every 10 minutes. This is 144 times/day. Each run generates
+ three lines of text, for a total of 432 lines of text I don't want to see.
+ Obviously this should be optional, but it would be nice if there were a
+ way to flag an entry so that it wasn't logged at all unless there was an
+ error.
+
+<< I don't know nothin' 'bout no /usr/lib/cron/log. What is this file?
+ I don't see any reason to create log entries, given the mail-the-
+ output behaviour. Opinions, anyone? >>
+
+I will come up with other ideas no doubt, but I can always implement them
+myself.
+
+<< That's what I like about PD software. Please send me the diffs! >>
+
+The other problem you have is making sure you can run standard
+crontabs. I would suggest something like this: if the command part of the
+entry starts with an unescaped -, then flags and options follow immediately
+thereafter. As in:
+
+2,12,22,32,42,52 * * * * -q /usr/lib/uucp/uudemon.hr
+
+This could mean do not log the uudemon.hr run unless there is a problem of
+some kind. This is probably safe as not many filenames start with "-", and
+those that do are already a problem for people.
+
+<< Since I don't plan on supporting /usr/lib/cron/log in ANY form unless
+ many people request it, I won't be needing -q as you've defined it.
+ I could use something like this to avoid sending mail on errors, for
+ the occasional script that you don't want to bullet-proof.
+
+ The compatibility issue is CRITICAL. The 4.3BSD crontab format is
+ a crime against the whole philosophy of Unix(TM), in my opinion. >>
+
+One other minor thing to consider is the ulimit: can different users get
+different ulimits for their crontab entries?
+
+<< Boy I'm ignorant today. What's a ulimit, and what should I do with
+ it in a crontab? Suggestions, enlightenment, etc ?? >>
+
+From qantel!lll-crg!ames!uw-beaver!uw-nsr!john Tue Jan 6 23:32:44 1987
+Date: Thu, 1 Jan 87 10:53:05 pst
+From: lll-crg!ames!uw-beaver!uw-nsr!john (John Sambrook 5-7433)
+To: vixie!paul
+Status: RO
+
+How about not hardwiring the default environment that cron builds for its
+children in the cron program itself? Our cron does this and it's the pits
+because we are TZ=PST8PDT not TZ=EST5EDT !
+
+<< yeachk. I assure you, I will not hardwire the TZ! >>
+From ptsfa!well!dv Fri Jan 9 04:01:50 1987
+Date: Thu, 8 Jan 87 23:50:40 pst
+From: well!dv (David W. Vezie)
+To: ptsfa!vixie!paul
+Status: RO
+
+6, have a special notation called 'H' which would expand to weekends
+ and holidays (you'd have to keep a database somewhere of real
+ holidays), and also 'W' for workdays (neither weekend or holiday).
+
+<< Too much work. There should be a standard way to define and
+ detect holidays under Unix(TM); if there were, I'd use it. As
+ it is, I'll leave this for someone else to add.
+
+ I can see the usefulness; it just doesn't quite seem worth it. >>
+From qantel!gatech!akgua!blnt1!jat Wed Jan 14 20:00:40 1987
+Date: Tue, 13 Jan 87 16:39:38 EST
+From: gatech!akgua!blnt1!jat
+Status: RO
+
+1) Add some way to tell cron to reread the files, say kill -1 <pid>
+
+<< whenever the 'crontab' program is run and updates a crontab file,
+ a file /usr/spool/cron/POKECRON is created; next time the cron
+ daemon wakes up, it sees it, and re-reads the crontab files.
+
+ I thought of handling the signal; even implemented it. Then this
+ clever idea hit me and I ripped it all out and added a single
+ IF-statement to handle the POKECRON file. >>
+
+2) Have some kind of retry time so that if a command fails, cron will try to
+ execute it again after a certain period. This is useful if you have some
+ type of cleanup program that can run at the scheduled time for some reason
+ (such as locked device, unmounted filesystem, etc).
+
+<< Hmmm, sounds useful. I could do this by submitting an 'at' job...
+ I'll think about it. >>
+From ptsfa!dual!ucbvax!ihnp4!mtuxo!ender Sat Jan 3 16:54:00 1987
+From: dual!ucbvax!ihnp4!mtuxo!ender
+Date: Sat, 3 Jan 87 14:05:13 PST
+To: ucbvax!dual!ptsfa!vixie!paul
+Status: RO
+
+It would be nice if nonprivileged users can setup personal crontab files
+(~/.cronrc, say) and be able to run personal jobs at regular intervals.
+
+<< this is done, but in the SysV style: the 'crontab' program installs
+ a new crontab file for the executing user (can be overridden by root
+ for setup of uucp and news). the advantage of this is that (1) when
+ a crontab is changed, the daemon can be informed automatically; and
+ (2) the file can be syntax-checked before installation. >>
+From ptsfa!ames!seismo!ihnp4!lcc!richard Fri Jan 16 04:47:33 1987
+Date: Fri, 16 Jan 87 07:44:57 EST
+To: nike!ptsfa!vixie!paul
+Status: RO
+
+The System V cron is nice, but it has a few annoying features. One is that
+its mail files will say that the previous message is the output of "one of your
+cron commands." I wish it would say WHICH cron command.
+
+<< Done. Also which shell, which user (useful when the mail gets
+ forwarded), which home directory, and other useful crud. >>
+
+Another problem is with timezones. It is necessary to specify TZ=PST8PDT (or
+whatever) when you invoke cron (from inittab, or /etc/rc) and it is also
+necessary to add TZ=PST8PDT to each crontab line which might need it. Cron
+should automatically export its idea of the "TZ" to each invoked command, and
+it should be possible to put a line in the crontab file which overrides that
+for every command in the file (e.g., most users are on EST, so cron is run
+with TZ=EST5EDT; but one user is usually on PST and wants all of his cron
+commands to run with TZ=PST8PDT). This might be extended to allow any
+environment variable to be specified once for the whole crontab file (e.g.,
+PATH).
+
+<< Well, since I run the user's shell, you could put this into .cshrc.
+ generic environment-variable setting could be useful, though. Since
+ I have to modify the environment anyway, I'll consider this. >>
+
+A log file might be a nice idea, but the System V cron log is too verbose.
+I seem to remember that cron keeps it open, too; so you can't even have
+something go and periodically clean it out.
+
+<< I don't do /usr/lib/cron/log. I wasn't aware of this file until I
+ got all these suggestions. Do people want this file? Tell me! >>
diff --git a/usr.sbin/cron/doc/Makefile.vixie b/usr.sbin/cron/doc/Makefile.vixie
new file mode 100644
index 000000000000..53b8e9b9bcc5
--- /dev/null
+++ b/usr.sbin/cron/doc/Makefile.vixie
@@ -0,0 +1,124 @@
+#/* Copyright 1988,1990,1993,1994 by Paul Vixie
+# * All rights reserved
+# */
+
+## Copyright (c) 1997 by Internet Software Consortium.
+##
+## Permission to use, copy, modify, and distribute this software for any
+## purpose with or without fee is hereby granted, provided that the above
+## copyright notice and this permission notice appear in all copies.
+##
+## THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+## ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+## OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+## CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+## DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+## PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+## ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+
+# Makefile for vixie's cron
+#
+# $Id: Makefile,v 1.2 1998/08/14 00:32:35 vixie Exp $
+#
+# vix 03mar88 [moved to RCS, rest of log is in there]
+# vix 30mar87 [goodbye, time.c; hello, getopt]
+# vix 12feb87 [cleanup for distribution]
+# vix 30dec86 [written]
+
+# NOTES:
+# 'make' can be done by anyone
+# 'make install' must be done by root
+#
+# this package needs getopt(3), bitstring(3), and BSD install(8).
+#
+# the configurable stuff in this makefile consists of compilation
+# options (use -O, cron runs forever) and destination directories.
+# SHELL is for the 'augumented make' systems where 'make' imports
+# SHELL from the environment and then uses it to run its commands.
+# if your environment SHELL variable is /bin/csh, make goes real
+# slow and sometimes does the wrong thing.
+#
+# this package needs the 'bitstring macros' library, which is
+# available from me or from the comp.sources.unix archive. if you
+# put 'bitstring.h' in a non-standard place (i.e., not intuited by
+# cc(1)), you will have to define INCLUDE to set the include
+# directory for cc. INCLUDE should be `-Isomethingorother'.
+#
+# there's more configuration info in config.h; edit that first!
+
+#################################### begin configurable stuff
+#<<DESTROOT is assumed to have ./etc, ./bin, and ./man subdirectories>>
+DESTROOT = $(DESTDIR)/usr
+DESTSBIN = $(DESTROOT)/sbin
+DESTBIN = $(DESTROOT)/bin
+DESTMAN = $(DESTROOT)/share/man
+#<<need bitstring.h>>
+INCLUDE = -I.
+#INCLUDE =
+#<<need getopt()>>
+LIBS =
+#<<optimize or debug?>>
+#CDEBUG = -O
+CDEBUG = -g
+#<<lint flags of choice?>>
+LINTFLAGS = -hbxa $(INCLUDE) $(DEBUGGING)
+#<<want to use a nonstandard CC?>>
+CC = gcc -Wall -Wno-unused -Wno-comment
+#<<manifest defines>>
+DEFS =
+#(SGI IRIX systems need this)
+#DEFS = -D_BSD_SIGNALS -Dconst=
+#<<the name of the BSD-like install program>>
+#INSTALL = installbsd
+INSTALL = install
+#<<any special load flags>>
+LDFLAGS =
+#################################### end configurable stuff
+
+SHELL = /bin/sh
+CFLAGS = $(CDEBUG) $(INCLUDE) $(DEFS)
+
+INFOS = README CHANGES FEATURES INSTALL CONVERSION THANKS MAIL
+MANPAGES = bitstring.3 crontab.5 crontab.1 cron.8 putman.sh
+HEADERS = bitstring.h cron.h config.h pathnames.h externs.h
+SOURCES = cron.c crontab.c database.c do_command.c entry.c \
+ env.c job.c user.c popen.c misc.c
+SHAR_SOURCE = $(INFOS) $(MANPAGES) Makefile $(HEADERS) $(SOURCES)
+LINT_CRON = cron.c database.c user.c entry.c \
+ misc.c job.c do_command.c env.c popen.c
+LINT_CRONTAB = crontab.c misc.c entry.c env.c
+CRON_OBJ = cron.o database.o user.o entry.o job.o do_command.o \
+ misc.o env.o popen.o
+CRONTAB_OBJ = crontab.o misc.o entry.o env.o
+
+all : cron crontab
+
+lint :
+ lint $(LINTFLAGS) $(LINT_CRON) $(LIBS) \
+ |grep -v "constant argument to NOT" 2>&1
+ lint $(LINTFLAGS) $(LINT_CRONTAB) $(LIBS) \
+ |grep -v "constant argument to NOT" 2>&1
+
+cron : $(CRON_OBJ)
+ $(CC) $(LDFLAGS) -o cron $(CRON_OBJ) $(LIBS)
+
+crontab : $(CRONTAB_OBJ)
+ $(CC) $(LDFLAGS) -o crontab $(CRONTAB_OBJ) $(LIBS)
+
+install : all
+ $(INSTALL) -c -m 111 -o root -s cron $(DESTSBIN)/
+ $(INSTALL) -c -m 4111 -o root -s crontab $(DESTBIN)/
+ sh putman.sh crontab.1 $(DESTMAN)
+ sh putman.sh cron.8 $(DESTMAN)
+ sh putman.sh crontab.5 $(DESTMAN)
+
+clean :; rm -f *.o cron crontab a.out core tags *~ #*
+
+tags :; ctags ${SOURCES}
+
+kit : $(SHAR_SOURCE)
+ makekit -m -s99k $(SHAR_SOURCE)
+
+$(CRON_OBJ) : cron.h config.h externs.h pathnames.h Makefile
+$(CRONTAB_OBJ) : cron.h config.h externs.h pathnames.h Makefile
diff --git a/usr.sbin/cron/doc/README b/usr.sbin/cron/doc/README
new file mode 100644
index 000000000000..ebd8849a52a0
--- /dev/null
+++ b/usr.sbin/cron/doc/README
@@ -0,0 +1,68 @@
+#/* Copyright 1988,1990,1993 by Paul Vixie <paul@vix.com>
+# * All rights reserved
+# */
+
+## Copyright (c) 1997 by Internet Software Consortium.
+##
+## Permission to use, copy, modify, and distribute this software for any
+## purpose with or without fee is hereby granted, provided that the above
+## copyright notice and this permission notice appear in all copies.
+##
+## THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+## ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+## OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+## CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+## DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+## PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+## ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+## SOFTWARE.
+
+Vixie Cron V4.0 - September 7, 1997
+[V3.1 was some time after 1993]
+[V3.0 was December 27, 1993]
+[V2.2 was some time in 1992]
+[V2.1 was May 29, 1991]
+[V2.0 was July 5, 1990]
+[V2.0-beta was December 9, 1988]
+[V1.0 was May 6, 1987]
+Paul Vixie
+
+This is a version of 'cron' that is known to run on most systems. It
+is functionally based on the SysV cron, which means that each user can have
+their own crontab file (all crontab files are stored in a read-protected
+directory, usually /var/cron/tabs). No direct support is provided for
+'at'; you can continue to run 'atrun' from the crontab as you have been
+doing. If you don't have atrun (i.e., System V) you are in trouble.
+
+A messages is logged each time a command is executed; also, the files
+"allow" and "deny" in /var/cron can be used to control access to the
+"crontab" command (which installs crontabs). It hasn't been tested on
+SysV, although some effort has gone into making the port an easy one.
+
+To use this: Sorry, folks, there is no cutesy 'Configure' script. You'll
+have to go edit a couple of files... So, here's the checklist:
+
+ Read all the FEATURES, INSTALL, and CONVERSION files
+ Edit config.h
+ Edit Makefile
+ (both of these files have instructions inside; note that
+ some things in config.h are definable in Makefile and are
+ therefore surrounded by #ifndef...#endif)
+ 'make'
+ 'su' and 'make install'
+ (you may have to install the man pages by hand)
+ kill your existing cron process
+ (actually you can run your existing cron if you want, but why?)
+ build new crontabs using /usr/lib/{crontab,crontab.local}
+ (either put them all in "root"'s crontab, or divide it up
+ and rip out all the 'su' commands, collapse the lengthy
+ lists into ranges with steps -- basically, this step is
+ as much work as you want to make it)
+ start up the new cron
+ (must be done as root)
+ watch it. test it with 'crontab -r' and watch the daemon track your
+ changes.
+ if you like it, change your /etc/{rc,rc.local} to use it instead of
+ the old one.
+
+$Id: README,v 1.2 1998/08/14 00:32:35 vixie Exp $
diff --git a/usr.sbin/cron/doc/README.1ST b/usr.sbin/cron/doc/README.1ST
new file mode 100644
index 000000000000..66cd0b237bd2
--- /dev/null
+++ b/usr.sbin/cron/doc/README.1ST
@@ -0,0 +1,4 @@
+Sources substantially rearranged 26 Aug 94, Jordan Hubbard. Content
+remains faithful to the original 3.0 cron source from Paul Vixie, but
+any bugs introduced by this reorganization or the port to FreeBSD remain
+purely my own. - Jordan
diff --git a/usr.sbin/cron/doc/THANKS b/usr.sbin/cron/doc/THANKS
new file mode 100644
index 000000000000..3787c2943d7a
--- /dev/null
+++ b/usr.sbin/cron/doc/THANKS
@@ -0,0 +1,29 @@
+15 January 1990
+Paul Vixie
+
+Many people have contributed to cron. Many more than I can remember, in fact.
+Rich Salz and Carl Gutekunst were each of enormous help to me in V1; Carl for
+helping me understand UNIX well enough to write it, and Rich for helping me
+get the features right.
+
+John Gilmore wrote me a wonderful review of V2, which took me a whole year to
+answer even though it made me clean up some really awful things in the code.
+(According to John the most awful things are still in here, of course.)
+
+Paul Close made a suggestion which led to /etc/crond.pid and the mutex locking
+on it. Kevin Braunsdorf of Purdue made a suggestion that led to @reboot and
+its brothers and sisters; he also sent some diffs that lead cron toward compil-
+ability with System V, though without at(1) capabilities, this cron isn't going
+to be that useful on System V. Bob Alverson fixed a silly bug in the line
+number counting. Brian Reid made suggestions which led to the run queue and
+the source-file labelling in installed crontabs.
+
+Scott Narveson ported V2 to a Sequent, and sent in the most useful single batch
+of diffs I got from anybody. Changes attributable to Scott are:
+ -> sendmail won't time out if the command is slow to generate output
+ -> day-of-week names aren't off by one anymore
+ -> crontab says the right thing if you do something you shouldn't do
+ -> crontab(5) man page is longer and more informative
+ -> misc changes related to the side effects of fclose()
+ -> Sequent "universe" support added (may also help on Pyramids)
+ -> null pw_shell is dealt with now; default is /bin/sh
diff --git a/usr.sbin/cron/lib/Makefile b/usr.sbin/cron/lib/Makefile
new file mode 100644
index 000000000000..475683cc7a6f
--- /dev/null
+++ b/usr.sbin/cron/lib/Makefile
@@ -0,0 +1,10 @@
+LIB= cron
+INTERNALLIB=
+SRCS= entry.c env.c misc.c
+
+WARNS?= 3
+
+CFLAGS+= -I${.CURDIR:H}/cron
+CFLAGS+= -DLOGIN_CAP -DPAM
+
+.include <bsd.lib.mk>
diff --git a/usr.sbin/cron/lib/Makefile.depend b/usr.sbin/cron/lib/Makefile.depend
new file mode 100644
index 000000000000..217d6b6b7bd8
--- /dev/null
+++ b/usr.sbin/cron/lib/Makefile.depend
@@ -0,0 +1,13 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/cron/lib/entry.c b/usr.sbin/cron/lib/entry.c
new file mode 100644
index 000000000000..9d9572c56dc8
--- /dev/null
+++ b/usr.sbin/cron/lib/entry.c
@@ -0,0 +1,729 @@
+/*
+ * Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$Id: entry.c,v 1.3 1998/08/14 00:32:39 vixie 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"
+#include <grp.h>
+#ifdef LOGIN_CAP
+#include <login_cap.h>
+#endif
+
+typedef enum ecode {
+ e_none, e_minute, e_hour, e_dom, e_month, e_dow,
+ e_cmd, e_timespec, e_username, e_group, e_option,
+ e_mem
+#ifdef LOGIN_CAP
+ , e_class
+#endif
+} ecode_e;
+
+static const 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",
+ "bad group name",
+ "bad option",
+ "out of memory",
+#ifdef LOGIN_CAP
+ "bad class name",
+#endif
+ };
+
+static char get_list(bitstr_t *, int, int, const char *[], int, FILE *),
+ get_range(bitstr_t *, int, int, const char *[], int, FILE *),
+ get_number(int *, int, const char *[], int, FILE *);
+static int set_element(bitstr_t *, int, int, int);
+
+void
+free_entry(entry *e)
+{
+#ifdef LOGIN_CAP
+ if (e->class != NULL)
+ free(e->class);
+#endif
+ if (e->cmd != NULL)
+ free(e->cmd);
+ if (e->envp != NULL)
+ 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 *file, void (*error_func)(const char *), 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;
+ int len;
+ char cmd[MAX_COMMAND];
+ char envstr[MAX_ENVSTR];
+ char **prev_env;
+
+ 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 (e == NULL) {
+ warn("load_entry: calloc failed");
+ return NULL;
+ }
+
+ if (ch == '@') {
+ long interval;
+ char *endptr;
+
+ /* 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
+ */
+ Debug(DPARS, ("load_entry()...about to test shortcuts\n"))
+ ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
+ if (!strcmp("reboot", cmd)) {
+ Debug(DPARS, ("load_entry()...reboot shortcut\n"))
+ e->flags |= WHEN_REBOOT;
+ } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
+ Debug(DPARS, ("load_entry()...yearly shortcut\n"))
+ bit_set(e->second, 0);
+ 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));
+ e->flags |= DOW_STAR;
+ } else if (!strcmp("monthly", cmd)) {
+ Debug(DPARS, ("load_entry()...monthly shortcut\n"))
+ bit_set(e->second, 0);
+ 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));
+ e->flags |= DOW_STAR;
+ } else if (!strcmp("weekly", cmd)) {
+ Debug(DPARS, ("load_entry()...weekly shortcut\n"))
+ bit_set(e->second, 0);
+ bit_set(e->minute, 0);
+ bit_set(e->hour, 0);
+ bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
+ e->flags |= DOM_STAR;
+ bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
+ bit_set(e->dow, 0);
+ } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
+ Debug(DPARS, ("load_entry()...daily shortcut\n"))
+ bit_set(e->second, 0);
+ 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)) {
+ Debug(DPARS, ("load_entry()...hourly shortcut\n"))
+ bit_set(e->second, 0);
+ bit_set(e->minute, 0);
+ bit_nset(e->hour, 0, (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 if (!strcmp("every_minute", cmd)) {
+ Debug(DPARS, ("load_entry()...every_minute shortcut\n"))
+ bit_set(e->second, 0);
+ bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1));
+ bit_nset(e->hour, 0, (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 if (!strcmp("every_second", cmd)) {
+ Debug(DPARS, ("load_entry()...every_second shortcut\n"))
+ e->flags |= SEC_RES;
+ bit_nset(e->second, 0, (LAST_SECOND-FIRST_SECOND+1));
+ bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1));
+ bit_nset(e->hour, 0, (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 if (*cmd != '\0' &&
+ (interval = strtol(cmd, &endptr, 10)) > 0 &&
+ *endptr == '\0') {
+ Debug(DPARS, ("load_entry()... %ld seconds "
+ "since last run\n", interval))
+ e->interval = interval;
+ e->flags = INTERVAL;
+ } else {
+ ecode = e_timespec;
+ goto eof;
+ }
+ /* Advance past whitespace between shortcut and
+ * username/command.
+ */
+ Skip_Blanks(ch, file);
+ if (ch == EOF) {
+ ecode = e_cmd;
+ goto eof;
+ }
+ } else {
+ Debug(DPARS, ("load_entry()...about to parse numerics\n"))
+ bit_set(e->second, 0);
+
+ 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 equivalent */
+ 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 */
+ char *s;
+ struct group *grp;
+#ifdef LOGIN_CAP
+ login_cap_t *lc;
+#endif
+
+ 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;
+ }
+
+ /* need to have consumed blanks when checking options below */
+ Skip_Blanks(ch, file)
+ unget_char(ch, file);
+#ifdef LOGIN_CAP
+ if ((s = strrchr(username, '/')) != NULL) {
+ *s = '\0';
+ e->class = strdup(s + 1);
+ if (e->class == NULL)
+ warn("strdup(\"%s\")", s + 1);
+ } else {
+ e->class = strdup(RESOURCE_RC);
+ if (e->class == NULL)
+ warn("strdup(\"%s\")", RESOURCE_RC);
+ }
+ if (e->class == NULL) {
+ ecode = e_mem;
+ goto eof;
+ }
+ if ((lc = login_getclass(e->class)) == NULL) {
+ ecode = e_class;
+ goto eof;
+ }
+ login_close(lc);
+#endif
+ grp = NULL;
+ if ((s = strrchr(username, ':')) != NULL) {
+ *s = '\0';
+ if ((grp = getgrnam(s + 1)) == NULL) {
+ ecode = e_group;
+ goto eof;
+ }
+ }
+
+ pw = getpwnam(username);
+ if (pw == NULL) {
+ ecode = e_username;
+ goto eof;
+ }
+ if (grp != NULL)
+ pw->pw_gid = grp->gr_gid;
+ Debug(DPARS, ("load_entry()...uid %d, gid %d\n",pw->pw_uid,pw->pw_gid))
+#ifdef LOGIN_CAP
+ Debug(DPARS, ("load_entry()...class %s\n",e->class))
+#endif
+ }
+
+#ifndef PAM /* PAM takes care of account expiration by itself */
+ if (pw->pw_expire && time(NULL) >= pw->pw_expire) {
+ ecode = e_username;
+ goto eof;
+ }
+#endif /* !PAM */
+
+ e->uid = pw->pw_uid;
+ e->gid = pw->pw_gid;
+
+ /* copy and fix up environment. some variables are just defaults and
+ * others are overrides; we process only the overrides here, defaults
+ * are handled in do_command after login.conf is processed.
+ */
+ e->envp = env_copy(envp);
+ if (e->envp == NULL) {
+ warn("env_copy");
+ ecode = e_mem;
+ goto eof;
+ }
+ if (!env_get("SHELL", e->envp)) {
+ prev_env = e->envp;
+ e->envp = env_set(e->envp, "SHELL=" _PATH_BSHELL);
+ if (e->envp == NULL) {
+ warn("env_set(%s)", "SHELL=" _PATH_BSHELL);
+ env_free(prev_env);
+ ecode = e_mem;
+ goto eof;
+ }
+ }
+ /* If LOGIN_CAP, this is deferred to do_command where the login class
+ * is processed. If !LOGIN_CAP, do it here.
+ */
+#ifndef LOGIN_CAP
+ if (!env_get("HOME", e->envp)) {
+ prev_env = e->envp;
+ len = snprintf(envstr, sizeof(envstr), "HOME=%s", pw->pw_dir);
+ if (len < (int)sizeof(envstr))
+ e->envp = env_set(e->envp, envstr);
+ if (len >= (int)sizeof(envstr) || e->envp == NULL) {
+ warn("env_set(%s)", envstr);
+ env_free(prev_env);
+ ecode = e_mem;
+ goto eof;
+ }
+ }
+#endif
+ prev_env = e->envp;
+ len = snprintf(envstr, sizeof(envstr), "LOGNAME=%s", pw->pw_name);
+ if (len < (int)sizeof(envstr))
+ e->envp = env_set(e->envp, envstr);
+ if (len >= (int)sizeof(envstr) || e->envp == NULL) {
+ warn("env_set(%s)", envstr);
+ env_free(prev_env);
+ ecode = e_mem;
+ goto eof;
+ }
+#if defined(BSD)
+ prev_env = e->envp;
+ len = snprintf(envstr, sizeof(envstr), "USER=%s", pw->pw_name);
+ if (len < (int)sizeof(envstr))
+ e->envp = env_set(e->envp, envstr);
+ if (len >= (int)sizeof(envstr) || e->envp == NULL) {
+ warn("env_set(%s)", envstr);
+ env_free(prev_env);
+ ecode = e_mem;
+ goto eof;
+ }
+#endif
+
+ Debug(DPARS, ("load_entry()...checking for command options\n"))
+
+ ch = get_char(file);
+
+ while (ch == '-') {
+ Debug(DPARS|DEXT, ("load_entry()...expecting option\n"))
+ switch (ch = get_char(file)) {
+ case 'n':
+ Debug(DPARS|DEXT, ("load_entry()...got MAIL_WHEN_ERR ('n') option\n"))
+ /* only allow the user to set the option once */
+ if ((e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR) {
+ Debug(DPARS|DEXT, ("load_entry()...duplicate MAIL_WHEN_ERR ('n') option\n"))
+ ecode = e_option;
+ goto eof;
+ }
+ e->flags |= MAIL_WHEN_ERR;
+ break;
+ case 'q':
+ Debug(DPARS|DEXT, ("load_entry()...got DONT_LOG ('q') option\n"))
+ /* only allow the user to set the option once */
+ if ((e->flags & DONT_LOG) == DONT_LOG) {
+ Debug(DPARS|DEXT, ("load_entry()...duplicate DONT_LOG ('q') option\n"))
+ ecode = e_option;
+ goto eof;
+ }
+ e->flags |= DONT_LOG;
+ break;
+ default:
+ Debug(DPARS|DEXT, ("load_entry()...invalid option '%c'\n", ch))
+ ecode = e_option;
+ goto eof;
+ }
+ ch = get_char(file);
+ if (ch!='\t' && ch!=' ') {
+ ecode = e_option;
+ goto eof;
+ }
+
+ Skip_Blanks(ch, file)
+ if (ch == EOF || ch == '\n') {
+ ecode = e_cmd;
+ goto eof;
+ }
+ }
+
+ unget_char(ch, file);
+
+ 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.
+ */
+ 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);
+ if (e->cmd == NULL) {
+ warn("strdup(\"%s\")", cmd);
+ ecode = e_mem;
+ goto eof;
+ }
+ Debug(DPARS, ("load_entry()...returning successfully\n"))
+
+ /* success, fini, return pointer to the entry we just created...
+ */
+ return e;
+
+ eof:
+ free_entry(e);
+ if (ecode != e_none && error_func)
+ (*error_func)(ecodes[(int)ecode]);
+ while (ch != EOF && ch != '\n')
+ ch = get_char(file);
+ return NULL;
+}
+
+
+/*
+ * bits one bit per flag, default=FALSE
+ * low, high bounds, impl. offset for bitstr
+ * names NULL or names for these elements
+ * ch current character being processed
+ * file file being read
+ */
+static char
+get_list(bitstr_t *bits, int low, int high, const char *names[], int ch,
+ FILE *file)
+{
+ 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;
+}
+
+
+/*
+ * bits one bit per flag, default=FALSE
+ * low, high bounds, impl. offset for bitstr
+ * names NULL or names for these elements
+ * ch current character being processed
+ * file file being read
+ */
+static char
+get_range(bitstr_t *bits, int low, int high, const char *names[], int ch,
+ FILE *file)
+{
+ /* range = number | number "-" number [ "/" number ]
+ */
+
+ int i, 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 == '/')
+ num2 = high;
+ else 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 || num3 == 0)
+ 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 implemented by paul vixie).
+ */
+ for (i = num1; i <= num2; i += num3)
+ if (EOF == set_element(bits, low, high, i))
+ return EOF;
+
+ return ch;
+}
+
+
+/*
+ * numptr where does the result go?
+ * low offset applied to enum result
+ * names symbolic names, if any, for enums
+ * ch current character
+ * file source
+ */
+static char
+get_number(int *numptr, int low, const char *names[], int ch, FILE *file)
+{
+ 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';
+ if (len == 0)
+ return (EOF);
+
+ /* 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(bitstr_t *bits, 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/lib/env.c b/usr.sbin/cron/lib/env.c
new file mode 100644
index 000000000000..287dd8636293
--- /dev/null
+++ b/usr.sbin/cron/lib/env.c
@@ -0,0 +1,260 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+
+
+#include "cron.h"
+
+
+char **
+env_init(void)
+{
+ char **p = (char **) malloc(sizeof(char **));
+
+ if (p)
+ p[0] = NULL;
+ return (p);
+}
+
+
+void
+env_free(char **envp)
+{
+ char **p;
+
+ if ((p = envp))
+ for (; *p; p++)
+ free(*p);
+ free(envp);
+}
+
+
+char **
+env_copy(char **envp)
+{
+ int count, i;
+ char **p;
+
+ for (count = 0; envp[count] != NULL; count++)
+ ;
+ p = (char **) malloc((count+1) * sizeof(char *)); /* 1 for the NULL */
+ if (p == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ for (i = 0; i < count; i++)
+ if ((p[i] = strdup(envp[i])) == NULL) {
+ while (--i >= 0)
+ (void) free(p[i]);
+ free(p);
+ errno = ENOMEM;
+ return NULL;
+ }
+ p[count] = NULL;
+ return (p);
+}
+
+
+char **
+env_set(char **envp, char *envstr)
+{
+ int count, found;
+ char **p;
+ char *q;
+
+ /*
+ * 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.
+ */
+ q = envp[found];
+ if ((envp[found] = strdup(envstr)) == NULL) {
+ envp[found] = q;
+ /* XXX env_free(envp); */
+ errno = ENOMEM;
+ return NULL;
+ }
+ free(q);
+ 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 *)));
+ if (p == NULL) {
+ /* XXX env_free(envp); */
+ errno = ENOMEM;
+ return NULL;
+ }
+ p[count] = p[count-1];
+ if ((p[count-1] = strdup(envstr)) == NULL) {
+ env_free(p);
+ errno = ENOMEM;
+ return NULL;
+ }
+ return (p);
+}
+
+
+/* return ERR = end of file
+ * FALSE = not an env setting (file was repositioned)
+ * TRUE = was an env setting
+ */
+int
+load_env(char *envstr, FILE *f)
+{
+ long filepos;
+ int fileline;
+ char name[MAX_ENVSTR], val[MAX_ENVSTR];
+ char quotechar, *c, *str;
+ int state;
+
+ /* The following states are traversed in order: */
+#define NAMEI 0 /* First char of NAME, may be quote */
+#define NAME 1 /* Subsequent chars of NAME */
+#define EQ1 2 /* After end of name, looking for '=' sign */
+#define EQ2 3 /* After '=', skipping whitespace */
+#define VALUEI 4 /* First char of VALUE, may be quote */
+#define VALUE 5 /* Subsequent chars of VALUE */
+#define FINI 6 /* All done, skipping trailing whitespace */
+#define ERROR 7 /* Error */
+
+ 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));
+
+ bzero (name, sizeof name);
+ bzero (val, sizeof val);
+ str = name;
+ state = NAMEI;
+ quotechar = '\0';
+ c = envstr;
+ while (state != ERROR && *c) {
+ switch (state) {
+ case NAMEI:
+ case VALUEI:
+ if (*c == '\'' || *c == '"')
+ quotechar = *c++;
+ ++state;
+ /* FALLTHROUGH */
+ case NAME:
+ case VALUE:
+ if (quotechar) {
+ if (*c == quotechar) {
+ state++;
+ c++;
+ break;
+ }
+ if (state == NAME && *c == '=') {
+ state = ERROR;
+ break;
+ }
+ } else {
+ if (state == NAME) {
+ if (isspace (*c)) {
+ c++;
+ state++;
+ break;
+ }
+ if (*c == '=') {
+ state++;
+ break;
+ }
+ }
+ }
+ *str++ = *c++;
+ break;
+
+ case EQ1:
+ if (*c == '=') {
+ state++;
+ str = val;
+ quotechar = '\0';
+ } else {
+ if (!isspace (*c))
+ state = ERROR;
+ }
+ c++;
+ break;
+ case EQ2:
+ case FINI:
+ if (isspace (*c))
+ c++;
+ else
+ state++;
+ break;
+ }
+ }
+ if (state != FINI && !(state == VALUE && !quotechar)) {
+ Debug(DPARS, ("load_env, parse error, state = %d\n", state))
+ fseek(f, filepos, 0);
+ Set_LineNum(fileline);
+ return (FALSE);
+ }
+ if (state == VALUE) {
+ /* End of unquoted value: trim trailing whitespace */
+ c = val + strlen (val);
+ while (c > val && isspace (*(c - 1)))
+ *(--c) = '\0';
+ }
+
+ /* 2 fields from parser; looks like an env setting */
+
+ if (snprintf(envstr, MAX_ENVSTR, "%s=%s", name, val) >= MAX_ENVSTR)
+ return (FALSE);
+ Debug(DPARS, ("load_env, <%s> <%s> -> <%s>\n", name, val, envstr))
+ return (TRUE);
+}
+
+
+char *
+env_get(char *name, char **envp)
+{
+ int len = strlen(name);
+ char *p, *q;
+
+ while ((p = *envp++) != NULL) {
+ if (!(q = strchr(p, '=')))
+ continue;
+ if ((q - p) == len && !strncmp(p, name, len))
+ return (q+1);
+ }
+ return (NULL);
+}
diff --git a/usr.sbin/cron/lib/misc.c b/usr.sbin/cron/lib/misc.c
new file mode 100644
index 000000000000..a67753cc1ff8
--- /dev/null
+++ b/usr.sbin/cron/lib/misc.c
@@ -0,0 +1,584 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ */
+
+/*
+ * Copyright (c) 1997 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+
+/* 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_CRON) && defined(LOG_FILE)
+# undef LOG_FILE
+#endif
+
+#if defined(LOG_DAEMON) && !defined(LOG_CRON)
+# define LOG_CRON LOG_DAEMON
+#endif
+
+
+static int LogFD = ERR;
+
+
+int
+strcmp_until(const char *left, const char *right, int until)
+{
+ while (*left && *left != until && *left == *right) {
+ left++;
+ right++;
+ }
+
+ if ((*left=='\0' || *left == until) &&
+ (*right=='\0' || *right == until)) {
+ return (0);
+ }
+ return (*left - *right);
+}
+
+
+/* strdtb(s) - delete trailing blanks in string 's' and return new length
+ */
+int
+strdtb(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(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) {
+ const char **test;
+ int mask;
+
+ /* try to find debug flag name in our list.
+ */
+ for ( test = DebugFlagNames, mask = 1;
+ *test != NULL && 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(void)
+{
+#if defined(BSD) || defined(POSIX)
+ if (seteuid(ROOT_UID) < OK)
+ err(ERROR_EXIT, "seteuid");
+#else
+ if (setuid(ROOT_UID) < OK)
+ err(ERROR_EXIT, "setuid");
+#endif
+}
+
+
+void
+set_cron_cwd(void)
+{
+ struct stat sb;
+
+ /* first check for CRONDIR ("/var/cron" or some such)
+ */
+ if (stat(CRONDIR, &sb) < OK && errno == ENOENT) {
+ warn("%s", CRONDIR);
+ if (OK == mkdir(CRONDIR, 0700)) {
+ warnx("%s: created", CRONDIR);
+ stat(CRONDIR, &sb);
+ } else {
+ err(ERROR_EXIT, "%s: mkdir", CRONDIR);
+ }
+ }
+ if (!(sb.st_mode & S_IFDIR))
+ err(ERROR_EXIT, "'%s' is not a directory, bailing out", CRONDIR);
+ if (chdir(CRONDIR) < OK)
+ err(ERROR_EXIT, "cannot chdir(%s), bailing out", CRONDIR);
+
+ /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such)
+ */
+ if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) {
+ warn("%s", SPOOL_DIR);
+ if (OK == mkdir(SPOOL_DIR, 0700)) {
+ warnx("%s: created", SPOOL_DIR);
+ stat(SPOOL_DIR, &sb);
+ } else {
+ err(ERROR_EXIT, "%s: mkdir", SPOOL_DIR);
+ }
+ }
+ if (!(sb.st_mode & S_IFDIR))
+ err(ERROR_EXIT, "'%s' is not a directory, bailing out", SPOOL_DIR);
+}
+
+
+/* get_char(file) : like getc() but increment LineNumber on newlines
+ */
+int
+get_char(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(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(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)
+{
+ 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(char *string, FILE *file)
+{
+ char line[MAX_TEMPSTR];
+
+ rewind(file);
+ while (fgets(line, MAX_TEMPSTR, file)) {
+ if (line[0] != '\0')
+ if (line[strlen(line)-1] == '\n')
+ 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(char *username)
+{
+ FILE *allow, *deny;
+ int isallowed;
+
+ isallowed = FALSE;
+
+ deny = NULL;
+#if defined(ALLOW_FILE) && defined(DENY_FILE)
+ if ((allow = fopen(ALLOW_FILE, "r")) == NULL && errno != ENOENT)
+ goto out;
+ if ((deny = fopen(DENY_FILE, "r")) == NULL && errno != ENOENT)
+ goto out;
+ Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
+#else
+ allow = NULL;
+#endif
+
+ if (allow)
+ isallowed = in_file(username, allow);
+ else if (deny)
+ isallowed = !in_file(username, deny);
+ else {
+#if defined(ALLOW_ONLY_ROOT)
+ isallowed = (strcmp(username, ROOT_USER) == 0);
+#else
+ isallowed = TRUE;
+#endif
+ }
+out: if (allow)
+ fclose(allow);
+ if (deny)
+ fclose(deny);
+ return (isallowed);
+}
+
+
+void
+log_it(const char *username, int xpid, const char *event, const char *detail)
+{
+#if defined(LOG_FILE) || DEBUGGING
+ PID_T pid = xpid;
+#endif
+#if defined(LOG_FILE)
+ char *msg;
+ TIME_T now = time((TIME_T) 0);
+ 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 (msg == NULL)
+ warnx("failed to allocate memory for log message");
+ else {
+ if (LogFD < OK) {
+ LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600);
+ if (LogFD < OK) {
+ warn("can't open log file %s", 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)
+ warn("%s", LOG_FILE);
+ warnx("can't write to log file");
+ 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(void)
+{
+ if (LogFD != ERR) {
+ close(LogFD);
+ LogFD = ERR;
+ }
+}
+
+
+/* two warnings:
+ * (1) this routine is fairly slow
+ * (2) it returns a pointer to static storage
+ * parameters:
+ * s: string we want the first word of
+ * t: terminators, implicitly including \0
+ */
+char *
+first_word(char *s, char *t)
+{
+ static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */
+ static int retsel = 0;
+ 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.
+ */
+static void
+mkprint(char *dst, unsigned char *src, int len)
+{
+ /*
+ * XXX
+ * We know this routine can't overflow the dst buffer because mkprints()
+ * allocated enough space for the worst case.
+ */
+ while (len-- > 0)
+ {
+ 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(unsigned char *src, unsigned int len)
+{
+ char *dst = malloc(len*4 + 1);
+
+ if (dst != NULL)
+ mkprint(dst, src, len);
+
+ return dst;
+}
+
+
+#ifdef MAIL_DATE
+/* Sat, 27 Feb 93 11:44:51 CST
+ * 123456789012345678901234567
+ */
+char *
+arpadate(time_t *clock)
+{
+ time_t t = clock ?*clock :time(0L);
+ struct tm *tm = localtime(&t);
+ static char ret[60]; /* zone name might be >3 chars */
+
+ if (tm->tm_year >= 100)
+ tm->tm_year += 1900;
+
+ (void) snprintf(ret, sizeof(ret), "%s, %2d %s %d %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(void) { save_euid = geteuid(); return seteuid(getuid()); }
+int swap_uids_back(void) { return seteuid(save_euid); }
+#else /*HAVE_SAVED_UIDS*/
+int swap_uids(void) { return setreuid(geteuid(), getuid()); }
+int swap_uids_back(void) { return swap_uids(); }
+#endif /*HAVE_SAVED_UIDS*/