diff options
Diffstat (limited to 'usr.sbin/cron')
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*/ |
