aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/cron/crontab
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/cron/crontab')
-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
5 files changed, 1212 insertions, 0 deletions
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;
+ }
+}