diff options
Diffstat (limited to 'usr.sbin/cron/crontab')
| -rw-r--r-- | usr.sbin/cron/crontab/Makefile | 16 | ||||
| -rw-r--r-- | usr.sbin/cron/crontab/Makefile.depend | 18 | ||||
| -rw-r--r-- | usr.sbin/cron/crontab/crontab.1 | 153 | ||||
| -rw-r--r-- | usr.sbin/cron/crontab/crontab.5 | 393 | ||||
| -rw-r--r-- | usr.sbin/cron/crontab/crontab.c | 632 |
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; + } +} |
