Subject: Vixie cron (finally) arrives in 2.11BSD (#422 - 3 of 3) Index: usr.sbin/cron 2.11BSD Description: The cron(8) program only allows for a single system wide crontab file. Individual users can not maintain their own crontab files. Repeat-By: Observation for many years ;) Fix: This is a 3 part update. This (#422) is part 3 of 3 and contains the first half of the new cron sources. The instructions are in part 1. As always this and previous updates to 2.11BSD are available via anonymous FTP to either FTP.IIPO.GTEGSC.COM or MOE.2BSD.COM in the directory /pub/2.11BSD. -----------------------------cut here----------------------- #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create: # cron/cron.8 # cron/cron.c # cron/cron.h # cron/crontab.1 # cron/crontab.5 # cron/crontab.c # cron/database.c # cron/do_command.c # cron/entry.c # cron/env.c # cron/externs.h # cron/job.c # cron/misc.c # cron/pathnames.h # cron/popen.c # cron/putman.sh # cron/user.c # This archive created: Tue Jul 20 20:08:44 1999 export PATH; PATH=/bin:/usr/bin:$PATH if test -f 'cron/cron.8' then echo shar: "will not over-write existing file 'cron/cron.8'" else sed 's/^Y//' << \SHAR_EOF > 'cron/cron.8' Y.\"/* Copyright 1988,1990,1993 by Paul Vixie Y.\" * All rights reserved Y.\" * Y.\" * Distribute freely, except: don't remove my name from the source or Y.\" * documentation (don't take credit for my work), mark your changes (don't Y.\" * get me blamed for your possible bugs), don't alter or remove this Y.\" * notice. May be sold if buildable source is provided to buyer. No Y.\" * warrantee of any kind, express or implied, is included with this Y.\" * software; use at your own risk, responsibility for damages (if any) to Y.\" * anyone resulting from the use of this software rests entirely with the Y.\" * user. Y.\" * Y.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y.\" * I'll try to keep a version up to date. I can be reached as follows: Y.\" * Paul Vixie uunet!decwrl!vixie!paul Y.\" */ Y.\" Y.\" $Id: cron.8,v 2.2 1993/12/28 08:34:43 vixie Exp $ Y.\" Y.TH CRON 8 "20 December 1993" Y.UC 4 Y.SH NAME Ycron \- daemon to execute scheduled commands (Vixie Cron) Y.SH SYNOPSIS Ycron Y.SH DESCRIPTION Y.I Cron Yshould be started from /etc/rc or /etc/rc.local. It will return immediately, Yso you don't need to start it with '&'. Y.PP Y.I Cron Ysearches /var/cron/tabs for crontab files which are named after accounts in Y/etc/passwd; crontabs found are loaded into memory. Y.I Cron Yalso searches for /etc/crontab which is in a different format (see Y.IR crontab(5)). Y.I Cron Ythen wakes up every minute, examining all stored crontabs, checking each Ycommand to see if it should be run in the current minute. When executing Ycommands, any output is mailed to the owner of the crontab (or to the user Ynamed in the MAILTO environment variable in the crontab, if such exists). Y.PP YAdditionally, Y.I cron Ychecks each minute to see if its spool directory's modtime (or the modtime Yon Y.IR /etc/crontab) Yhas changed, and if it has, Y.I cron Ywill then examine the modtime on all crontabs and reload those which have Ychanged. Thus Y.I cron Yneed not be restarted whenever a crontab file is modified. Note that the Y.IR Crontab (1) Ycommand updates the modtime of the spool directory whenever it changes a Ycrontab. Y.SH "SEE ALSO" Ycrontab(1), crontab(5) Y.SH AUTHOR Y.nf YPaul Vixie SHAR_EOF chmod 644 'cron/cron.8' fi if test -f 'cron/cron.c' then echo shar: "will not over-write existing file 'cron/cron.c'" else sed 's/^Y//' << \SHAR_EOF > 'cron/cron.c' Y/* Copyright 1988,1990,1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y#if !defined(lint) && !defined(LINT) Ystatic char rcsid[] = "$Id: cron.c,v 2.11 1994/01/15 20:43:43 vixie Exp $"; Y#endif Y Y Y#define MAIN_PROGRAM Y Y Y#include "cron.h" Y#include Y#include Y Ystatic void usage __P((void)), Y run_reboot_jobs __P((cron_db *)), Y cron_tick __P((cron_db *)), Y cron_sync __P((void)), Y cron_sleep __P((void)), Y sigchld_handler __P((int)), Y sighup_handler __P((int)), Y parse_args __P((int c, char *v[])); Y Y Ystatic void Yusage() { Y fprintf(stderr, "usage: %s [-x debugflag[,...]]\n", ProgramName); Y exit(ERROR_EXIT); Y} Y Y Yint Ymain(argc, argv) Y int argc; Y char *argv[]; Y{ Y cron_db database; Y Y ProgramName = argv[0]; Y Y setlinebuf(stdout); Y setlinebuf(stderr); Y Y parse_args(argc, argv); Y Y (void) signal(SIGCHLD, sigchld_handler); Y (void) signal(SIGHUP, sighup_handler); Y Y acquire_daemonlock(0); Y set_cron_uid(); Y set_cron_cwd(); Y Y setenv("PATH", _PATH_DEFPATH, 1); Y Y /* if there are no debug flags turned on, fork as a daemon should. Y */ Y# if DEBUGGING Y if (DebugFlags) { Y# else Y if (0) { Y# endif Y (void) fprintf(stderr, "[%d] cron started\n", getpid()); Y } else { Y switch (fork()) { Y case -1: Y log_it("CRON",getpid(),"DEATH","can't fork"); Y exit(0); Y break; Y case 0: Y /* child process */ Y log_it("CRON",getpid(),"STARTUP","fork ok"); Y (void) setsid(); Y break; Y default: Y /* parent process should just die */ Y _exit(0); Y } Y } Y Y acquire_daemonlock(0); Y database.head = NULL; Y database.tail = NULL; Y database.mtime = (time_t) 0; Y load_database(&database); Y run_reboot_jobs(&database); Y cron_sync(); Y while (TRUE) { Y# if DEBUGGING Y if (!(DebugFlags & DTEST)) Y# endif /*DEBUGGING*/ Y cron_sleep(); Y Y load_database(&database); Y Y /* do this iteration Y */ Y cron_tick(&database); Y Y /* sleep 1 minute Y */ Y TargetTime += 60; Y } Y} Y Y Ystatic void Yrun_reboot_jobs(db) Y cron_db *db; Y{ Y register user *u; Y register entry *e; Y Y for (u = db->head; u != NULL; u = u->next) { Y for (e = u->crontab; e != NULL; e = e->next) { Y if (e->flags & WHEN_REBOOT) { Y job_add(e, u); Y } Y } Y } Y (void) job_runqueue(); Y} Y Y Ystatic void Ycron_tick(db) Y cron_db *db; Y{ Y register struct tm *tm = localtime(&TargetTime); Y register int minute, hour, dom, month, dow; Y register user *u; Y register entry *e; Y Y /* make 0-based values out of these so we can use them as indicies Y */ Y minute = tm->tm_min -FIRST_MINUTE; Y hour = tm->tm_hour -FIRST_HOUR; Y dom = tm->tm_mday -FIRST_DOM; Y month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; Y dow = tm->tm_wday -FIRST_DOW; Y Y Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n", Y getpid(), minute, hour, dom, month, dow)) Y Y /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the Y * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* Y * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this Y * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. Y * like many bizarre things, it's the standard. Y */ Y for (u = db->head; u != NULL; u = u->next) { Y for (e = u->crontab; e != NULL; e = e->next) { Y Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n", Y env_get("LOGNAME", e->envp), Y e->uid, e->gid, e->cmd)) Y if (bit_test(e->minute, minute) Y && bit_test(e->hour, hour) Y && bit_test(e->month, month) Y && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) Y ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) Y : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) Y ) Y ) { Y job_add(e, u); Y } Y } Y } Y} Y Y Y/* the task here is to figure out how long it's going to be until :00 of the Y * following minute and initialize TargetTime to this value. TargetTime Y * will subsequently slide 60 seconds at a time, with correction applied Y * implicitly in cron_sleep(). it would be nice to let cron execute in Y * the "current minute" before going to sleep, but by restarting cron you Y * could then get it to execute a given minute's jobs more than once. Y * instead we have the chance of missing a minute's jobs completely, but Y * that's something sysadmin's know to expect what with crashing computers.. Y */ Ystatic void Ycron_sync() { Y register struct tm *tm; Y Y TargetTime = time((time_t*)0); Y tm = localtime(&TargetTime); Y TargetTime += (60 - tm->tm_sec); Y} Y Y Ystatic void Ycron_sleep() { Y register int seconds_to_wait; Y Y do { Y seconds_to_wait = (int) (TargetTime - time((time_t*)0)); Y Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n", Y getpid(), TargetTime, seconds_to_wait)) Y Y /* if we intend to sleep, this means that it's finally Y * time to empty the job queue (execute it). Y * Y * if we run any jobs, we'll probably screw up our timing, Y * so go recompute. Y * Y * note that we depend here on the left-to-right nature Y * of &&, and the short-circuiting. Y */ Y } while (seconds_to_wait > 0 && job_runqueue()); Y Y while (seconds_to_wait > 0) { Y Debug(DSCH, ("[%d] sleeping for %d seconds\n", Y getpid(), seconds_to_wait)) Y seconds_to_wait = (int) sleep((unsigned int) seconds_to_wait); Y } Y} Y Y Ystatic void Ysigchld_handler(x) { Y WAIT_T waiter; Y PID_T pid; Y Y for (;;) { Y pid = waitpid(-1, &waiter, WNOHANG); Y switch (pid) { Y case -1: Y Debug(DPROC, Y ("[%d] sigchld...no children\n", getpid())) Y return; Y case 0: Y Debug(DPROC, Y ("[%d] sigchld...no dead kids\n", getpid())) Y return; Y default: Y Debug(DPROC, Y ("[%d] sigchld...pid #%d died, stat=%d\n", Y getpid(), pid, WEXITSTATUS(waiter))) Y } Y } Y} Y Ystatic void Ysighup_handler(x) { Y log_close(); Y} Y Y Ystatic void Yparse_args(argc, argv) Y int argc; Y char *argv[]; Y{ Y int argch; Y Y while (EOF != (argch = getopt(argc, argv, "x:"))) { Y switch (argch) { Y default: Y usage(); Y case 'x': Y if (!set_debug_flags(optarg)) Y usage(); Y break; Y } Y } Y} SHAR_EOF chmod 644 'cron/cron.c' fi if test -f 'cron/cron.h' then echo shar: "will not over-write existing file 'cron/cron.h'" else sed 's/^Y//' << \SHAR_EOF > 'cron/cron.h' Y/* Copyright 1988,1990,1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y/* cron.h - header for vixie's cron Y * Y * $Id: cron.h,v 2.10 1994/01/15 20:43:43 vixie Exp $ Y * Y * vix 14nov88 [rest of log is in RCS] Y * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley] Y * vix 30dec86 [written] Y */ Y Y/* reorder these #include's at your peril */ Y Y#include Y#include Y#include "compat.h" Y Y#include Y#include Y#include Y#include Y#include Y Y#include "pathnames.h" Y#include "config.h" Y#include "externs.h" Y Y /* these are really immutable, and are Y * defined for symbolic convenience only Y * TRUE, FALSE, and ERR must be distinct Y * ERR must be < OK. Y */ Y#define TRUE 1 Y#define FALSE 0 Y /* system calls return this on success */ Y#define OK 0 Y /* or this on error */ Y#define ERR (-1) Y Y /* turn this on to get '-x' code */ Y#ifndef DEBUGGING Y#define DEBUGGING FALSE Y#endif Y Y#define READ_PIPE 0 /* which end of a pipe pair do you read? */ Y#define WRITE_PIPE 1 /* or write to? */ Y#define STDIN 0 /* what is stdin's file descriptor? */ Y#define STDOUT 1 /* stdout's? */ Y#define STDERR 2 /* stderr's? */ Y#define ERROR_EXIT 1 /* exit() with this will scare the shell */ Y#define OK_EXIT 0 /* exit() with this is considered 'normal' */ Y#define MAX_FNAME 100 /* max length of internally generated fn */ Y#define MAX_COMMAND 1000 /* max length of internally generated cmd */ Y#define MAX_ENVSTR 1000 /* max length of envvar=value\0 strings */ Y#define MAX_TEMPSTR 100 /* obvious */ Y#define MAX_UNAME 20 /* max length of username, should be overkill */ Y#define ROOT_UID 0 /* don't change this, it really must be root */ Y#define ROOT_USER "root" /* ditto */ Y Y /* NOTE: these correspond to DebugFlagNames, Y * defined below. Y */ Y#define DEXT 0x0001 /* extend flag for other debug masks */ Y#define DSCH 0x0002 /* scheduling debug mask */ Y#define DPROC 0x0004 /* process control debug mask */ Y#define DPARS 0x0008 /* parsing debug mask */ Y#define DLOAD 0x0010 /* database loading debug mask */ Y#define DMISC 0x0020 /* misc debug mask */ Y#define DTEST 0x0040 /* test mode: don't execute any commands */ Y#define DBIT 0x0080 /* bit twiddling shown (long) */ Y Y#define CRON_TAB(u) "%s/%s", SPOOL_DIR, u Y#define REG register Y#define PPC_NULL ((char **)NULL) Y Y#ifndef MAXHOSTNAMELEN Y#define MAXHOSTNAMELEN 64 Y#endif Y Y#define Skip_Blanks(c, f) \ Y while (c == '\t' || c == ' ') \ Y c = get_char(f); Y Y#define Skip_Nonblanks(c, f) \ Y while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \ Y c = get_char(f); Y Y#define Skip_Line(c, f) \ Y do {c = get_char(f);} while (c != '\n' && c != EOF); Y Y#if DEBUGGING Y# define Debug(mask, message) \ Y if ( (DebugFlags & (mask) ) == (mask) ) \ Y printf message; Y#else /* !DEBUGGING */ Y# define Debug(mask, message) \ Y ; Y#endif /* DEBUGGING */ Y Y#define Set_LineNum(ln) {Debug(DPARS|DEXT,("linenum=%d\n",ln)); \ Y LineNumber = ln; \ Y } Y Y#define FIRST_MINUTE 0 Y#define LAST_MINUTE 59 Y#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1) Y Y#define FIRST_HOUR 0 Y#define LAST_HOUR 23 Y#define HOUR_COUNT (LAST_HOUR - FIRST_HOUR + 1) Y Y#define FIRST_DOM 1 Y#define LAST_DOM 31 Y#define DOM_COUNT (LAST_DOM - FIRST_DOM + 1) Y Y#define FIRST_MONTH 1 Y#define LAST_MONTH 12 Y#define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1) Y Y/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */ Y#define FIRST_DOW 0 Y#define LAST_DOW 7 Y#define DOW_COUNT (LAST_DOW - FIRST_DOW + 1) Y Y /* each user's crontab will be held as a list of Y * the following structure. Y * Y * These are the cron commands. Y */ Y Ytypedef struct _entry { Y struct _entry *next; Y uid_t uid; Y gid_t gid; Y char **envp; Y char *cmd; Y bitstr_t bit_decl(minute, MINUTE_COUNT); Y bitstr_t bit_decl(hour, HOUR_COUNT); Y bitstr_t bit_decl(dom, DOM_COUNT); Y bitstr_t bit_decl(month, MONTH_COUNT); Y bitstr_t bit_decl(dow, DOW_COUNT); Y int flags; Y#define DOM_STAR 0x01 Y#define DOW_STAR 0x02 Y#define WHEN_REBOOT 0x04 Y} entry; Y Y /* the crontab database will be a list of the Y * following structure, one element per user Y * plus one for the system. Y * Y * These are the crontabs. Y */ Y Ytypedef struct _user { Y struct _user *next, *prev; /* links */ Y char *name; Y time_t mtime; /* last modtime of crontab */ Y entry *crontab; /* this person's crontab */ Y} user; Y Ytypedef struct _cron_db { Y user *head, *tail; /* links */ Y time_t mtime; /* last modtime on spooldir */ Y} cron_db; Y Y Yvoid set_cron_uid __P((void)), Y set_cron_cwd __P((void)), Y load_database __P((cron_db *)), Y open_logfile __P((void)), Y sigpipe_func __P((void)), Y job_add __P((entry *, user *)), Y do_command __P((entry *, user *)), Y link_user __P((cron_db *, user *)), Y unlink_user __P((cron_db *, user *)), Y free_user __P((user *)), Y env_free __P((char **)), Y unget_char __P((int, FILE *)), Y free_entry __P((entry *)), Y acquire_daemonlock __P((int)), Y skip_comments __P((FILE *)), Y log_it __P((char *, int, char *, char *)), Y log_close __P((void)); Y Yint job_runqueue __P((void)), Y set_debug_flags __P((char *)), Y get_char __P((FILE *)), Y get_string __P((char *, int, FILE *, char *)), Y swap_uids __P((void)), Y load_env __P((char *, FILE *)), Y cron_pclose __P((FILE *)), Y strcmp_until __P((char *, char *, int)), Y allowed __P((char *)), Y strdtb __P((char *)); Y Ychar *env_get __P((char *, char **)), Y *arpadate __P((time_t *)), Y *mkprints __P((unsigned char *, unsigned int)), Y *first_word __P((char *, char *)), Y **env_init __P((void)), Y **env_copy __P((char **)), Y **env_set __P((char **, char *)); Y Yuser *load_user __P((int, struct passwd *, char *)), Y *find_user __P((cron_db *, char *)); Y Yentry *load_entry __P((FILE *, void (*)(), Y struct passwd *, char **)); Y YFILE *cron_popen __P((char *, char *)); Y Y Y /* in the C tradition, we only create Y * variables for the main program, just Y * extern them elsewhere. Y */ Y Y#ifdef MAIN_PROGRAM Y# if !defined(LINT) && !defined(lint) Ychar *copyright[] = { Y "@(#) Copyright 1988,1989,1990,1993,1994 by Paul Vixie", Y "@(#) All rights reserved" Y }; Y# endif Y Ychar *MonthNames[] = { Y "Jan", "Feb", "Mar", "Apr", "May", "Jun", Y "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", Y NULL Y }; Y Ychar *DowNames[] = { Y "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", Y NULL Y }; Y Ychar *ProgramName; Yint LineNumber; Ytime_t TargetTime; Y Y# if DEBUGGING Yint DebugFlags; Ychar *DebugFlagNames[] = { /* sync with #defines */ Y "ext", "sch", "proc", "pars", "load", "misc", "test", "bit", Y NULL /* NULL must be last element */ Y }; Y# endif /* DEBUGGING */ Y#else /*MAIN_PROGRAM*/ Yextern char *copyright[], Y *MonthNames[], Y *DowNames[], Y *ProgramName; Yextern int LineNumber; Yextern time_t TargetTime; Y# if DEBUGGING Yextern int DebugFlags; Yextern char *DebugFlagNames[]; Y# endif /* DEBUGGING */ Y#endif /*MAIN_PROGRAM*/ SHAR_EOF chmod 644 'cron/cron.h' fi if test -f 'cron/crontab.1' then echo shar: "will not over-write existing file 'cron/crontab.1'" else sed 's/^Y//' << \SHAR_EOF > 'cron/crontab.1' Y.\"/* Copyright 1988,1990,1993 by Paul Vixie Y.\" * All rights reserved Y.\" * Y.\" * Distribute freely, except: don't remove my name from the source or Y.\" * documentation (don't take credit for my work), mark your changes (don't Y.\" * get me blamed for your possible bugs), don't alter or remove this Y.\" * notice. May be sold if buildable source is provided to buyer. No Y.\" * warrantee of any kind, express or implied, is included with this Y.\" * software; use at your own risk, responsibility for damages (if any) to Y.\" * anyone resulting from the use of this software rests entirely with the Y.\" * user. Y.\" * Y.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y.\" * I'll try to keep a version up to date. I can be reached as follows: Y.\" * Paul Vixie uunet!decwrl!vixie!paul Y.\" */ Y.\" Y.\" $Id: crontab.1,v 2.4 1993/12/31 10:47:33 vixie Exp $ Y.\" Y.TH CRONTAB 1 "29 December 1993" Y.UC 4 Y.SH NAME Ycrontab \- maintain crontab files for individual users (V3) Y.SH SYNOPSIS Ycrontab [ -u user ] file Y.br Ycrontab [ -u user ] { -l | -r | -e } Y.SH DESCRIPTION Y.I Crontab Yis the program used to install, deinstall or list the tables Yused to drive the Y.IR cron (8) Ydaemon in Vixie Cron. Each user can have their own crontab, and though Ythese are files in /var, they are not intended to be edited directly. Y.PP YIf the Y.I allow Yfile exists, then you must be listed therein in order to be allowed to use Ythis command. If the Y.I allow Yfile does not exist but the Y.I deny Yfile does exist, then you must \fBnot\fR be listed in the Y.I deny Yfile in order to use this command. If neither of these files exists, then Ydepending on site-dependent configuration parameters, only the super user Ywill be allowed to use this command, or all users will be able to use this Ycommand. Y.PP YIf the Y.I -u Yoption is given, it specifies the name of the user whose crontab is to be Ytweaked. If this option is not given, Y.I crontab Yexamines "your" crontab, i.e., the crontab of the person executing the Ycommand. Note that Y.IR su (8) Ycan confuse Y.I crontab Yand that if you are running inside of Y.IR su (8) Yyou should always use the Y.I -u Yoption for safety's sake. Y.PP YThe first form of this command is used to install a new crontab from some Ynamed file or standard input if the pseudo-filename ``-'' is given. Y.PP YThe Y.I -l Yoption causes the current crontab to be displayed on standard output. Y.PP YThe Y.I -r Yoption causes the current crontab to be removed. Y.PP YThe Y.I -e Yoption is used to edit the current crontab using the editor specified by Ythe \s-1VISUAL\s+1 or \s-1EDITOR\s+1 environment variables. After you exit Yfrom the editor, the modified crontab will be installed automatically. Y.SH "SEE ALSO" Ycrontab(5), cron(8) Y.SH FILES Y.nf Y/var/cron/allow Y/var/cron/deny Y.fi Y.SH STANDARDS YThe Y.I crontab Ycommand conforms to IEEE Std1003.2-1992 (``POSIX''). This new command syntax Ydiffers from previous versions of Vixie Cron, as well as from the classic YSVR3 syntax. Y.SH DIAGNOSTICS YA fairly informative usage message appears if you run it with a bad command Yline. Y.SH AUTHOR Y.nf YPaul Vixie SHAR_EOF chmod 644 'cron/crontab.1' fi if test -f 'cron/crontab.5' then echo shar: "will not over-write existing file 'cron/crontab.5'" else sed 's/^Y//' << \SHAR_EOF > 'cron/crontab.5' Y.\"/* Copyright 1988,1990,1993,1994 by Paul Vixie Y.\" * All rights reserved Y.\" * Y.\" * Distribute freely, except: don't remove my name from the source or Y.\" * documentation (don't take credit for my work), mark your changes (don't Y.\" * get me blamed for your possible bugs), don't alter or remove this Y.\" * notice. May be sold if buildable source is provided to buyer. No Y.\" * warrantee of any kind, express or implied, is included with this Y.\" * software; use at your own risk, responsibility for damages (if any) to Y.\" * anyone resulting from the use of this software rests entirely with the Y.\" * user. Y.\" * Y.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y.\" * I'll try to keep a version up to date. I can be reached as follows: Y.\" * Paul Vixie uunet!decwrl!vixie!paul Y.\" */ Y.\" Y.\" $Id: crontab.5,v 2.4 1994/01/15 20:43:43 vixie Exp $ Y.\" Y.TH CRONTAB 5 "24 January 1994" Y.UC 4 Y.SH NAME Ycrontab \- tables for driving cron Y.SH DESCRIPTION YA Y.I crontab Yfile contains instructions to the Y.IR cron (8) Ydaemon of the general form: ``run this command at this time on this date''. YEach user has their own crontab, and commands in any given crontab will be Yexecuted as the user who owns the crontab. Uucp and News will usually have Ytheir own crontabs, eliminating the need for explicitly running Y.IR su (1) Yas part of a cron command. Y.PP YBlank lines and leading spaces and tabs are ignored. Lines whose first Ynon-space character is a pound-sign (#) are comments, and are ignored. YNote that comments are not allowed on the same line as cron commands, since Ythey will be taken to be part of the command. Similarly, comments are not Yallowed on the same line as environment variable settings. Y.PP YAn active line in a crontab will be either an environment setting or a cron Ycommand. An environment setting is of the form, Y.PP Y name = value Y.PP Ywhere the spaces around the equal-sign (=) are optional, and any subsequent Ynon-leading spaces in Y.I value Ywill be part of the value assigned to Y.IR name . YThe Y.I value Ystring may be placed in quotes (single or double, but matching) to preserve Yleading or trailing blanks. Y.PP YSeveral environment variables are set up Yautomatically by the Y.IR cron (8) Ydaemon. YSHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd Yline of the crontab's owner. YHOME and SHELL may be overridden by settings in the crontab; LOGNAME may not. Y.PP Y(Another note: the LOGNAME variable is sometimes called USER on BSD systems... Yon these systems, USER will be set also.) Y.PP YIn addition to LOGNAME, HOME, and SHELL, Y.IR cron (8) Ywill look at MAILTO if it has any reason to send mail as a result of running Ycommands in ``this'' crontab. If MAILTO is defined (and non-empty), mail is Ysent to the user so named. If MAILTO is defined but empty (MAILTO=""), no Ymail will be sent. Otherwise mail is sent to the owner of the crontab. This Yoption is useful if you decide on /bin/mail instead of /usr/lib/sendmail as Yyour mailer when you install cron -- /bin/mail doesn't do aliasing, and UUCP Yusually doesn't read its mail. Y.PP YThe format of a cron command is very much the V7 standard, with a number of Yupward-compatible extensions. Each line has five time and date fields, Yfollowed by a user name if this is the system crontab file, Yfollowed by a command. Commands are executed by Y.IR cron (8) Ywhen the minute, hour, and month of year fields match the current time, Y.I and Ywhen at least one of the two day fields (day of month, or day of week) Ymatch the current time (see ``Note'' below). Y.IR cron (8) Yexamines cron entries once every minute. YThe time and date fields are: Y.IP Y.ta 1.5i Yfield allowed values Y.br Y----- -------------- Y.br Yminute 0-59 Y.br Yhour 0-23 Y.br Yday of month 0-31 Y.br Ymonth 0-12 (or names, see below) Y.br Yday of week 0-7 (0 or 7 is Sun, or use names) Y.br Y.PP YA field may be an asterisk (*), which always stands for ``first\-last''. Y.PP YRanges of numbers are allowed. Ranges are two numbers separated Ywith a hyphen. The specified range is inclusive. For example, Y8-11 for an ``hours'' entry specifies execution at hours 8, 9, 10 Yand 11. Y.PP YLists are allowed. A list is a set of numbers (or ranges) Yseparated by commas. Examples: ``1,2,5,9'', ``0-4,8-12''. Y.PP YStep values can be used in conjunction with ranges. Following Ya range with ``/'' specifies skips of the number's value Ythrough the range. For example, ``0-23/2'' can be used in the hours Yfield to specify command execution every other hour (the alternative Yin the V7 standard is ``0,2,4,6,8,10,12,14,16,18,20,22''). Steps are Yalso permitted after an asterisk, so if you want to say ``every two Yhours'', just use ``*/2''. Y.PP YNames can also be used for the ``month'' and ``day of week'' Yfields. Use the first three letters of the particular Yday or month (case doesn't matter). Ranges or Ylists of names are not allowed. Y.PP YThe ``sixth'' field (the rest of the line) specifies the command to be Yrun. YThe entire command portion of the line, up to a newline or % Ycharacter, will be executed by /bin/sh or by the shell Yspecified in the SHELL variable of the cronfile. YPercent-signs (%) in the command, unless escaped with backslash Y(\\), will be changed into newline characters, and all data Yafter the first % will be sent to the command as standard Yinput. Y.PP YNote: The day of a command's execution can be specified by two Yfields \(em day of month, and day of week. If both fields are Yrestricted (ie, aren't *), the command will be run when Y.I either Yfield matches the current time. For example, Y.br Y``30 4 1,15 * 5'' Ywould cause a command to be run at 4:30 am on the 1st and 15th of each Ymonth, plus every Friday. Y.SH EXAMPLE CRON FILE Y.nf Y Y# use /bin/sh to run commands, no matter what /etc/passwd says YSHELL=/bin/sh Y# mail any output to `paul', no matter whose crontab this is YMAILTO=paul Y# Y# run five minutes after midnight, every day Y5 0 * * * $HOME/bin/daily.job >> $HOME/tmp/out 2>&1 Y# run at 2:15pm on the first of every month -- output mailed to paul Y15 14 1 * * $HOME/bin/monthly Y# run at 10 pm on weekdays, annoy Joe Y0 22 * * 1-5 mail -s "It's 10pm" joe%Joe,%%Where are your kids?% Y23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday" Y5 4 * * sun echo "run at 5 after 4 every sunday" Y.fi Y.SH SEE ALSO Ycron(8), crontab(1) Y.SH EXTENSIONS YWhen specifying day of week, both day 0 and day 7 will be considered Sunday. YBSD and ATT seem to disagree about this. Y.PP YLists and ranges are allowed to co-exist in the same field. "1-3,7-9" would Ybe rejected by ATT or BSD cron -- they want to see "1-3" or "7,8,9" ONLY. Y.PP YRanges can include "steps", so "1-9/2" is the same as "1,3,5,7,9". Y.PP YNames of months or days of the week can be specified by name. Y.PP YEnvironment variables can be set in the crontab. In BSD or ATT, the Yenvironment handed to child processes is basically the one from /etc/rc. Y.PP YCommand output is mailed to the crontab owner (BSD can't do this), can be Ymailed to a person other than the crontab owner (SysV can't do this), or the Yfeature can be turned off and no mail will be sent at all (SysV can't do this Yeither). Y.SH AUTHOR Y.nf YPaul Vixie SHAR_EOF chmod 644 'cron/crontab.5' fi if test -f 'cron/crontab.c' then echo shar: "will not over-write existing file 'cron/crontab.c'" else sed 's/^Y//' << \SHAR_EOF > 'cron/crontab.c' Y/* Copyright 1988,1990,1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y#if !defined(lint) && !defined(LINT) Ystatic char rcsid[] = "$Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $"; Y#endif Y Y/* crontab - install and manage per-user crontab files Y * vix 02may87 [RCS has the rest of the log] Y * vix 26jan87 [original] Y */ Y Y Y#define MAIN_PROGRAM Y Y Y#include "cron.h" Y#include Y#include Y#include Y#include Y#include Y Y#define NHEADER_LINES 3 Y Yenum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace }; Y Y#if DEBUGGING Ystatic char *Options[] = { "???", "list", "delete", "edit", "replace" }; Y#endif Y Y Ystatic PID_T Pid; Ystatic char User[MAX_UNAME], RealUser[MAX_UNAME]; Ystatic char Filename[MAX_FNAME]; Ystatic FILE *NewCrontab; Ystatic int CheckErrorCount; Ystatic enum opt_t Option; Ystatic struct passwd *pw; Ystatic void list_cmd __P((void)), Y delete_cmd __P((void)), Y edit_cmd __P((void)), Y poke_daemon __P((void)), Y check_error __P((char *)), Y parse_args __P((int c, char *v[])); Ystatic int replace_cmd __P((void)); Y Y Ystatic void Yusage(msg) Y char *msg; Y{ Y fprintf(stderr, "%s: usage error: %s\n", ProgramName, msg); Y fprintf(stderr, "usage:\t%s [-u user] file\n", ProgramName); Y fprintf(stderr, "\t%s [-u user] { -e | -l | -r }\n", ProgramName); Y fprintf(stderr, "\t\t(default operation is replace, per 1003.2)\n"); Y fprintf(stderr, "\t-e\t(edit user's crontab)\n"); Y fprintf(stderr, "\t-l\t(list user's crontab)\n"); Y fprintf(stderr, "\t-r\t(delete user's crontab)\n"); Y exit(ERROR_EXIT); Y} Y Y Yint Ymain(argc, argv) Y int argc; Y char *argv[]; Y{ Y int exitstatus; Y Y Pid = getpid(); Y ProgramName = argv[0]; Y Y setlinebuf(stderr); Y Y parse_args(argc, argv); /* sets many globals, opens a file */ Y set_cron_uid(); Y set_cron_cwd(); Y if (!allowed(User)) { Y fprintf(stderr, Y "You (%s) are not allowed to use this program (%s)\n", Y User, ProgramName); Y fprintf(stderr, "See crontab(1) for more information\n"); Y log_it(RealUser, Pid, "AUTH", "crontab command not allowed"); Y exit(ERROR_EXIT); Y } Y exitstatus = OK_EXIT; Y switch (Option) { Y case opt_list: list_cmd(); Y break; Y case opt_delete: delete_cmd(); Y break; Y case opt_edit: edit_cmd(); Y break; Y case opt_replace: if (replace_cmd() < 0) Y exitstatus = ERROR_EXIT; Y break; Y } Y exit(0); Y /*NOTREACHED*/ Y} Y Y Ystatic void Yparse_args(argc, argv) Y int argc; Y char *argv[]; Y{ Y int argch; Y Y if (!(pw = getpwuid(getuid()))) { Y fprintf(stderr, "%s: your UID isn't in the passwd file.\n", Y ProgramName); Y fprintf(stderr, "bailing out.\n"); Y exit(ERROR_EXIT); Y } Y strcpy(User, pw->pw_name); Y strcpy(RealUser, User); Y Filename[0] = '\0'; Y Option = opt_unknown; Y while (EOF != (argch = getopt(argc, argv, "u:lerx:"))) { Y switch (argch) { Y case 'x': Y if (!set_debug_flags(optarg)) Y usage("bad debug option"); Y break; Y case 'u': Y if (getuid() != ROOT_UID) Y { Y fprintf(stderr, Y "must be privileged to use -u\n"); Y exit(ERROR_EXIT); Y } Y if (!(pw = getpwnam(optarg))) Y { Y fprintf(stderr, "%s: user `%s' unknown\n", Y ProgramName, optarg); Y exit(ERROR_EXIT); Y } Y (void) strcpy(User, optarg); Y break; Y case 'l': Y if (Option != opt_unknown) Y usage("only one operation permitted"); Y Option = opt_list; Y break; Y case 'r': Y if (Option != opt_unknown) Y usage("only one operation permitted"); Y Option = opt_delete; Y break; Y case 'e': Y if (Option != opt_unknown) Y usage("only one operation permitted"); Y Option = opt_edit; Y break; Y default: Y usage("unrecognized option"); Y } Y } Y Y endpwent(); Y Y if (Option != opt_unknown) { Y if (argv[optind] != NULL) { Y usage("no arguments permitted after this option"); Y } Y } else { Y if (argv[optind] != NULL) { Y Option = opt_replace; Y (void) strcpy (Filename, argv[optind]); Y } else { Y usage("file name must be specified for replace"); Y } Y } Y Y if (Option == opt_replace) { Y /* we have to open the file here because we're going to Y * chdir(2) into /var/cron before we get around to Y * reading the file. Y */ Y if (!strcmp(Filename, "-")) { Y NewCrontab = stdin; Y } else { Y /* relinquish the setuid status of the binary during Y * the open, lest nonroot users read files they should Y * not be able to read. we can't use access() here Y * since there's a race condition. thanks go out to Y * Arnt Gulbrandsen for spotting Y * the race. Y */ Y Y if (swap_uids() < OK) { Y perror("swapping uids"); Y exit(ERROR_EXIT); Y } Y if (!(NewCrontab = fopen(Filename, "r"))) { Y perror(Filename); Y exit(ERROR_EXIT); Y } Y if (swap_uids() < OK) { Y perror("swapping uids back"); Y exit(ERROR_EXIT); Y } Y } Y } Y Y Debug(DMISC, ("user=%s, file=%s, option=%s\n", Y User, Filename, Options[(int)Option])) Y} Y Y Ystatic void Ylist_cmd() { Y char n[MAX_FNAME]; Y register FILE *f; Y int ch; Y Y log_it(RealUser, Pid, "LIST", User); Y (void) sprintf(n, CRON_TAB(User)); Y if (!(f = fopen(n, "r"))) { Y if (errno == ENOENT) Y fprintf(stderr, "no crontab for %s\n", User); Y else Y perror(n); Y exit(ERROR_EXIT); Y } Y Y /* file is open. copy to stdout, close. Y */ Y Set_LineNum(1) Y while (EOF != (ch = get_char(f))) Y putchar(ch); Y fclose(f); Y} Y Y Ystatic void Ydelete_cmd() { Y char n[MAX_FNAME]; Y Y log_it(RealUser, Pid, "DELETE", User); Y (void) sprintf(n, CRON_TAB(User)); Y if (unlink(n)) { Y if (errno == ENOENT) Y fprintf(stderr, "no crontab for %s\n", User); Y else Y perror(n); Y exit(ERROR_EXIT); Y } Y poke_daemon(); Y} Y Y Ystatic void Ycheck_error(msg) Y char *msg; Y{ Y CheckErrorCount++; Y fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg); Y} Y Y Ystatic void Yedit_cmd() { Y char n[MAX_FNAME], q[MAX_TEMPSTR], *editor; Y register FILE *f; Y register int ch; Y int t, x; Y struct stat statbuf; Y time_t mtime; Y WAIT_T waiter; Y PID_T pid, xpid; Y Y log_it(RealUser, Pid, "BEGIN EDIT", User); Y (void) sprintf(n, CRON_TAB(User)); Y if (!(f = fopen(n, "r"))) { Y if (errno != ENOENT) { Y perror(n); Y exit(ERROR_EXIT); Y } Y fprintf(stderr, "no crontab for %s - using an empty one\n", Y User); Y if (!(f = fopen("/dev/null", "r"))) { Y perror("/dev/null"); Y exit(ERROR_EXIT); Y } Y } Y Y (void) sprintf(Filename, "/tmp/crontab.%d", Pid); Y if (-1 == (t = open(Filename, O_CREAT|O_EXCL|O_RDWR, 0600))) { Y perror(Filename); Y goto fatal; Y } Y if (fchown(t, getuid(), getgid()) < 0) { Y perror("fchown"); Y goto fatal; Y } Y if (!(NewCrontab = fdopen(t, "r+"))) { Y perror("fdopen"); Y goto fatal; Y } Y Y Set_LineNum(1) Y Y /* ignore the top few comments since we probably put them there. Y */ Y for (x = 0; x < NHEADER_LINES; x++) { Y ch = get_char(f); Y if (EOF == ch) Y break; Y if ('#' != ch) { Y putc(ch, NewCrontab); Y break; Y } Y while (EOF != (ch = get_char(f))) Y if (ch == '\n') Y break; Y if (EOF == ch) Y break; Y } Y Y /* copy the rest of the crontab (if any) to the temp file. Y */ Y if (EOF != ch) Y while (EOF != (ch = get_char(f))) Y putc(ch, NewCrontab); Y fclose(f); Y if (fflush(NewCrontab) < OK) { Y perror(Filename); Y exit(ERROR_EXIT); Y } Y again: Y rewind(NewCrontab); Y if (ferror(NewCrontab)) { Y fprintf(stderr, "%s: error while writing new crontab to %s\n", Y ProgramName, Filename); Y fatal: unlink(Filename); Y exit(ERROR_EXIT); Y } Y if (fstat(t, &statbuf) < 0) { Y perror("fstat"); Y goto fatal; Y } Y mtime = statbuf.st_mtime; Y Y if ((!(editor = getenv("VISUAL"))) Y && (!(editor = getenv("EDITOR"))) Y ) { Y editor = EDITOR; Y } Y Y /* we still have the file open. editors will generally rewrite the Y * original file rather than renaming/unlinking it and starting a Y * new one; even backup files are supposed to be made by copying Y * rather than by renaming. if some editor does not support this, Y * then don't use it. the security problems are more severe if we Y * close and reopen the file around the edit. Y */ Y Y switch (pid = fork()) { Y case -1: Y perror("fork"); Y goto fatal; Y case 0: Y /* child */ Y if (setuid(getuid()) < 0) { Y perror("setuid(getuid())"); Y exit(ERROR_EXIT); Y } Y if (chdir("/tmp") < 0) { Y perror("chdir(/tmp)"); Y exit(ERROR_EXIT); Y } Y if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR) { Y fprintf(stderr, "%s: editor or filename too long\n", Y ProgramName); Y exit(ERROR_EXIT); Y } Y sprintf(q, "%s %s", editor, Filename); Y execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, NULL); Y perror(editor); Y exit(ERROR_EXIT); Y /*NOTREACHED*/ Y default: Y /* parent */ Y break; Y } Y Y /* parent */ Y xpid = wait(&waiter); Y if (xpid != pid) { Y fprintf(stderr, "%s: wrong PID (%d != %d) from \"%s\"\n", Y ProgramName, xpid, pid, editor); Y goto fatal; Y } Y if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) { Y fprintf(stderr, "%s: \"%s\" exited with status %d\n", Y ProgramName, editor, WEXITSTATUS(waiter)); Y goto fatal; Y } Y if (WIFSIGNALED(waiter)) { Y fprintf(stderr, Y "%s: \"%s\" killed; signal %d (%score dumped)\n", Y ProgramName, editor, WTERMSIG(waiter), Y WCOREDUMP(waiter) ?"" :"no "); Y goto fatal; Y } Y if (fstat(t, &statbuf) < 0) { Y perror("fstat"); Y goto fatal; Y } Y if (mtime == statbuf.st_mtime) { Y fprintf(stderr, "%s: no changes made to crontab\n", Y ProgramName); Y goto remove; Y } Y fprintf(stderr, "%s: installing new crontab\n", ProgramName); Y switch (replace_cmd()) { Y case 0: Y break; Y case -1: Y for (;;) { Y printf("Do you want to retry the same edit? "); Y fflush(stdout); Y q[0] = '\0'; Y (void) fgets(q, sizeof q, stdin); Y switch (islower(q[0]) ? q[0] : tolower(q[0])) { Y case 'y': Y goto again; Y case 'n': Y goto abandon; Y default: Y fprintf(stderr, "Enter Y or N\n"); Y } Y } Y /*NOTREACHED*/ Y case -2: Y abandon: Y fprintf(stderr, "%s: edits left in %s\n", Y ProgramName, Filename); Y goto done; Y default: Y fprintf(stderr, "%s: panic: bad switch() in replace_cmd()\n"); Y goto fatal; Y } Y remove: Y unlink(Filename); Y done: Y log_it(RealUser, Pid, "END EDIT", User); Y} Y Y Y/* returns 0 on success Y * -1 on syntax error Y * -2 on install error Y */ Ystatic int Yreplace_cmd() { Y char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME]; Y register FILE *tmp; Y register int ch; Y int eof; Y entry *e; Y time_t now = time(NULL); Y char **envp = env_init(); Y Y (void) sprintf(n, "tmp.%d", Pid); Y (void) sprintf(tn, CRON_TAB(n)); Y if (!(tmp = fopen(tn, "w+"))) { Y perror(tn); Y return (-2); Y } Y Y /* write a signature at the top of the file. Y * Y * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. Y */ Y fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n"); Y fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); Y fprintf(tmp, "# (Cron version -- %s)\n", rcsid); Y Y /* copy the crontab to the tmp Y */ Y rewind(NewCrontab); Y Set_LineNum(1) Y while (EOF != (ch = get_char(NewCrontab))) Y putc(ch, tmp); Y ftruncate(fileno(tmp), ftell(tmp)); Y fflush(tmp); rewind(tmp); Y Y if (ferror(tmp)) { Y fprintf(stderr, "%s: error while writing new crontab to %s\n", Y ProgramName, tn); Y fclose(tmp); unlink(tn); Y return (-2); Y } Y Y /* check the syntax of the file being installed. Y */ Y Y /* BUG: was reporting errors after the EOF if there were any errors Y * in the file proper -- kludged it by stopping after first error. Y * vix 31mar87 Y */ Y Set_LineNum(1 - NHEADER_LINES) Y CheckErrorCount = 0; eof = FALSE; Y while (!CheckErrorCount && !eof) { Y switch (load_env(envstr, tmp)) { Y case ERR: Y eof = TRUE; Y break; Y case FALSE: Y e = load_entry(tmp, check_error, pw, envp); Y if (e) Y free(e); Y break; Y case TRUE: Y break; Y } Y } Y Y if (CheckErrorCount != 0) { Y fprintf(stderr, "errors in crontab file, can't install.\n"); Y fclose(tmp); unlink(tn); Y return (-1); Y } Y Y if (fchown(fileno(tmp), ROOT_UID, -1) < OK) Y { Y perror("chown"); Y fclose(tmp); unlink(tn); Y return (-2); Y } Y Y if (fchmod(fileno(tmp), 0600) < OK) Y { Y perror("chown"); Y fclose(tmp); unlink(tn); Y return (-2); Y } Y Y if (fclose(tmp) == EOF) { Y perror("fclose"); Y unlink(tn); Y return (-2); Y } Y Y (void) sprintf(n, CRON_TAB(User)); Y if (rename(tn, n)) { Y fprintf(stderr, "%s: error renaming %s to %s\n", Y ProgramName, tn, n); Y perror("rename"); Y unlink(tn); Y return (-2); Y } Y log_it(RealUser, Pid, "REPLACE", User); Y Y poke_daemon(); Y Y return (0); Y} Y Y Ystatic void Ypoke_daemon() { Y struct timeval tvs[2]; Y Y (void) gettimeofday(&tvs[0], NULL); Y tvs[1] = tvs[0]; Y if (utimes(SPOOL_DIR, tvs) < OK) { Y fprintf(stderr, "crontab: can't update mtime on spooldir\n"); Y perror(SPOOL_DIR); Y return; Y } Y} SHAR_EOF chmod 644 'cron/crontab.c' fi if test -f 'cron/database.c' then echo shar: "will not over-write existing file 'cron/database.c'" else sed 's/^Y//' << \SHAR_EOF > 'cron/database.c' Y/* Copyright 1988,1990,1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y#if !defined(lint) && !defined(LINT) Ystatic char rcsid[] = "$Id: database.c,v 2.8 1994/01/15 20:43:43 vixie Exp $"; Y#endif Y Y/* vix 26jan87 [RCS has the log] Y */ Y Y Y#include "cron.h" Y#include Y#include Y#include Y Y Y#define TMAX(a,b) ((a)>(b)?(a):(b)) Y Y Ystatic void process_crontab __P((char *, char *, char *, Y struct stat *, Y cron_db *, cron_db *)); Y Y Yvoid Yload_database(old_db) Y cron_db *old_db; Y{ Y DIR *dir; Y struct stat statbuf; Y struct stat syscron_stat; Y register DIR_T *dp; Y cron_db new_db; Y user *u, *nu; Y Y Debug(DLOAD, ("[%d] load_database()\n", getpid())) Y Y /* before we start loading any data, do a stat on SPOOL_DIR Y * so that if anything changes as of this moment (i.e., before we've Y * cached any of the database), we'll see the changes next time. Y */ Y if (stat(SPOOL_DIR, &statbuf) < OK) { Y log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR); Y (void) exit(ERROR_EXIT); Y } Y Y /* track system crontab file Y */ Y if (stat(SYSCRONTAB, &syscron_stat) < OK) Y syscron_stat.st_mtime = 0; Y Y /* if spooldir's mtime has not changed, we don't need to fiddle with Y * the database. Y * Y * Note that old_db->mtime is initialized to 0 in main(), and Y * so is guaranteed to be different than the stat() mtime the first Y * time this function is called. Y */ Y if (old_db->mtime == TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) { Y Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n", Y getpid())) Y return; Y } Y Y /* something's different. make a new database, moving unchanged Y * elements from the old database, reloading elements that have Y * actually changed. Whatever is left in the old database when Y * we're done is chaff -- crontabs that disappeared. Y */ Y new_db.mtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime); Y new_db.head = new_db.tail = NULL; Y Y if (syscron_stat.st_mtime) { Y process_crontab("root", "*system*", Y SYSCRONTAB, &syscron_stat, Y &new_db, old_db); Y } Y Y /* we used to keep this dir open all the time, for the sake of Y * efficiency. however, we need to close it in every fork, and Y * we fork a lot more often than the mtime of the dir changes. Y */ Y if (!(dir = opendir(SPOOL_DIR))) { Y log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR); Y (void) exit(ERROR_EXIT); Y } Y Y while (NULL != (dp = readdir(dir))) { Y char fname[MAXNAMLEN+1], Y tabname[MAXNAMLEN+1]; Y Y /* avoid file names beginning with ".". this is good Y * because we would otherwise waste two guaranteed calls Y * to getpwnam() for . and .., and also because user names Y * starting with a period are just too nasty to consider. Y */ Y if (dp->d_name[0] == '.') Y continue; Y Y (void) strcpy(fname, dp->d_name); Y sprintf(tabname, CRON_TAB(fname)); Y Y process_crontab(fname, fname, tabname, Y &statbuf, &new_db, old_db); Y } Y closedir(dir); Y Y /* if we don't do this, then when our children eventually call Y * getpwnam() in do_command.c's child_process to verify MAILTO=, Y * they will screw us up (and v-v). Y */ Y endpwent(); Y Y /* whatever's left in the old database is now junk. Y */ Y Debug(DLOAD, ("unlinking old database:\n")) Y for (u = old_db->head; u != NULL; u = nu) { Y Debug(DLOAD, ("\t%s\n", u->name)) Y nu = u->next; Y unlink_user(old_db, u); Y free_user(u); Y } Y Y /* overwrite the database control block with the new one. Y */ Y *old_db = new_db; Y Debug(DLOAD, ("load_database is done\n")) Y} Y Y Yvoid Ylink_user(db, u) Y register cron_db *db; Y register user *u; Y{ Y if (db->head == NULL) Y db->head = u; Y if (db->tail) Y db->tail->next = u; Y u->prev = db->tail; Y u->next = NULL; Y db->tail = u; Y} Y Y Yvoid Yunlink_user(db, u) Y register cron_db *db; Y register user *u; Y{ Y if (u->prev == NULL) Y db->head = u->next; Y else Y u->prev->next = u->next; Y Y if (u->next == NULL) Y db->tail = u->prev; Y else Y u->next->prev = u->prev; Y} Y Y Yuser * Yfind_user(db, name) Y cron_db *db; Y register char *name; Y{ Y char *env_get(); Y register user *u; Y Y for (u = db->head; u != NULL; u = u->next) Y if (!strcmp(u->name, name)) Y break; Y return u; Y} Y Y Ystatic void Yprocess_crontab(uname, fname, tabname, statbuf, new_db, old_db) Y char *uname; Y char *fname; Y char *tabname; Y struct stat *statbuf; Y cron_db *new_db; Y cron_db *old_db; Y{ Y struct passwd *pw = NULL; Y int crontab_fd = OK - 1; Y register user *u; Y Y if (strcmp(fname, "*system*") && !(pw = getpwnam(uname))) { Y /* file doesn't have a user in passwd file. Y */ Y log_it(fname, getpid(), "ORPHAN", "no passwd entry"); Y goto next_crontab; Y } Y Y if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) { Y /* crontab not accessible? Y */ Y log_it(fname, getpid(), "CAN'T OPEN", tabname); Y goto next_crontab; Y } Y Y if (fstat(crontab_fd, statbuf) < OK) { Y log_it(fname, getpid(), "FSTAT FAILED", tabname); Y goto next_crontab; Y } Y Y Debug(DLOAD, ("\t%s:", fname)) Y u = find_user(old_db, fname); Y if (u != NULL) { Y /* if crontab has not changed since we last read it Y * in, then we can just use our existing entry. Y */ Y if (u->mtime == statbuf->st_mtime) { Y Debug(DLOAD, (" [no change, using old data]")) Y unlink_user(old_db, u); Y link_user(new_db, u); Y goto next_crontab; Y } Y Y /* before we fall through to the code that will reload Y * the user, let's deallocate and unlink the user in Y * the old database. This is more a point of memory Y * efficiency than anything else, since all leftover Y * users will be deleted from the old database when Y * we finish with the crontab... Y */ Y Debug(DLOAD, (" [delete old data]")) Y unlink_user(old_db, u); Y free_user(u); Y log_it(fname, getpid(), "RELOAD", tabname); Y } Y u = load_user(crontab_fd, pw, fname); Y if (u != NULL) { Y u->mtime = statbuf->st_mtime; Y link_user(new_db, u); Y } Y Ynext_crontab: Y if (crontab_fd >= OK) { Y Debug(DLOAD, (" [done]\n")) Y close(crontab_fd); Y } Y} SHAR_EOF chmod 644 'cron/database.c' fi if test -f 'cron/do_command.c' then echo shar: "will not over-write existing file 'cron/do_command.c'" else sed 's/^Y//' << \SHAR_EOF > 'cron/do_command.c' Y/* Copyright 1988,1990,1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y#if !defined(lint) && !defined(LINT) Ystatic char rcsid[] = "$Id: do_command.c,v 2.12 1994/01/15 20:43:43 vixie Exp $"; Y#endif Y Y#include "cron.h" Y#include Y#if defined(SYSLOG) Y# include Y#endif Y Ystatic void child_process __P((entry *, user *)); Y Yvoid Ydo_command(e, u) Y entry *e; Y user *u; Y{ Y Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", Y getpid(), e->cmd, u->name, e->uid, e->gid)) Y Y /* fork to become asynchronous -- parent process is done immediately, Y * and continues to run the normal cron code, which means return to Y * tick(). the child and grandchild don't leave this function, alive. Y * Y * vfork() is unsuitable, since we have much to do, and the parent Y * needs to be able to run off and fork other processes. Y */ Y switch (fork()) { Y case -1: Y log_it("CRON",getpid(),"error","can't fork"); Y break; Y case 0: Y /* child process */ Y acquire_daemonlock(1); Y child_process(e, u); Y Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) Y _exit(OK_EXIT); Y break; Y default: Y /* parent process */ Y break; Y } Y Debug(DPROC, ("[%d] main process returning to work\n", getpid())) Y} Y Y Ystatic void Ychild_process(e, u) Y entry *e; Y user *u; Y{ Y int stdin_pipe[2], stdout_pipe[2]; Y register char *input_data; Y char *usernm, *mailto; Y register int ch; Y int children = 0, escaped; Y Y Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) Y Y /* mark ourselves as different to PS command watchers by upshifting Y * our program name. This has no effect on some kernels. Y */ Y for (input_data = ProgramName; ch = *input_data; input_data++) Y *input_data = (islower(ch) ? toupper(ch) : ch); Y Y /* discover some useful and important environment settings Y */ Y usernm = env_get("LOGNAME", e->envp); Y mailto = env_get("MAILTO", e->envp); Y Y /* our parent is watching for our death by catching SIGCHLD. we Y * do not care to watch for our children's deaths this way -- we Y * use wait() explictly. so we have to disable the signal (which Y * was inherited from the parent). Y */ Y (void) signal(SIGCHLD, SIG_IGN); Y Y /* create some pipes to talk to our future child Y */ Y pipe(stdin_pipe); /* child's stdin */ Y pipe(stdout_pipe); /* child's stdout */ Y Y /* since we are a forked process, we can diddle the command string Y * we were passed -- nobody else is going to use it again, right? Y * Y * if a % is present in the command, previous characters are the Y * command, and subsequent characters are the additional input to Y * the command. Subsequent %'s will be transformed into newlines, Y * but that happens later. Y */ Y escaped = FALSE; Y for (input_data = e->cmd; ch = *input_data; input_data++) { Y if (escaped) { Y escaped = FALSE; Y continue; Y } Y if (ch == '\\') { Y escaped = TRUE; Y continue; Y } Y if (ch == '%') { Y *input_data++ = '\0'; Y break; Y } Y } Y Y /* fork again, this time so we can exec the user's command. Y */ Y switch (vfork()) { Y case -1: Y log_it("CRON",getpid(),"error","can't vfork"); Y exit(ERROR_EXIT); Y /*NOTREACHED*/ Y case 0: Y Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", Y getpid())) Y Y /* write a log message. we've waited this long to do it Y * because it was not until now that we knew the PID that Y * the actual user command shell was going to get and the Y * PID is part of the log message. Y */ Y /*local*/{ Y char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); Y Y log_it(usernm, getpid(), "CMD", x); Y free(x); Y } Y Y /* that's the last thing we'll log. close the log files. Y */ Y#ifdef SYSLOG Y closelog(); Y#endif Y Y /* get new pgrp, void tty, etc. Y */ Y (void) setsid(); Y Y /* close the pipe ends that we won't use. this doesn't affect Y * the parent, who has to read and write them; it keeps the Y * kernel from recording us as a potential client TWICE -- Y * which would keep it from sending SIGPIPE in otherwise Y * appropriate circumstances. Y */ Y close(stdin_pipe[WRITE_PIPE]); Y close(stdout_pipe[READ_PIPE]); Y Y /* grandchild process. make std{in,out} be the ends of Y * pipes opened by our daddy; make stderr go to stdout. Y */ Y close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); Y close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); Y close(STDERR); dup2(STDOUT, STDERR); Y Y /* close the pipes we just dup'ed. The resources will remain. Y */ Y close(stdin_pipe[READ_PIPE]); Y close(stdout_pipe[WRITE_PIPE]); Y Y /* set our directory, uid and gid. Set gid first, since once Y * we set uid, we've lost root privledges. Y */ Y setgid(e->gid); Y initgroups(env_get("LOGNAME", e->envp), e->gid); Y setuid(e->uid); /* we aren't root after this... */ Y chdir(env_get("HOME", e->envp)); Y Y /* exec the command. Y */ Y { Y char *shell = env_get("SHELL", e->envp); Y Y# if DEBUGGING Y if (DebugFlags & DTEST) { Y fprintf(stderr, Y "debug DTEST is on, not exec'ing command.\n"); Y fprintf(stderr, Y "\tcmd='%s' shell='%s'\n", e->cmd, shell); Y _exit(OK_EXIT); Y } Y# endif /*DEBUGGING*/ Y execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); Y fprintf(stderr, "execl: couldn't exec `%s'\n", shell); Y perror("execl"); Y _exit(ERROR_EXIT); Y } Y break; Y default: Y /* parent process */ Y break; Y } Y Y children++; Y Y /* middle process, child of original cron, parent of process running Y * the user's command. Y */ Y Y Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) Y Y /* close the ends of the pipe that will only be referenced in the Y * grandchild process... Y */ Y close(stdin_pipe[READ_PIPE]); Y close(stdout_pipe[WRITE_PIPE]); Y Y /* Y * write, to the pipe connected to child's stdin, any input specified Y * after a % in the crontab entry. while we copy, convert any Y * additional %'s to newlines. when done, if some characters were Y * written and the last one wasn't a newline, write a newline. Y * Y * Note that if the input data won't fit into one pipe buffer (2K Y * or 4K on most BSD systems), and the child doesn't read its stdin, Y * we would block here. thus we must fork again. Y */ Y Y if (*input_data && fork() == 0) { Y register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); Y register int need_newline = FALSE; Y register int escaped = FALSE; Y register int ch; Y Y Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) Y Y /* close the pipe we don't use, since we inherited it and Y * are part of its reference count now. Y */ Y close(stdout_pipe[READ_PIPE]); Y Y /* translation: Y * \% -> % Y * % -> \n Y * \x -> \x for all x != % Y */ Y while (ch = *input_data++) { Y if (escaped) { Y if (ch != '%') Y putc('\\', out); Y } else { Y if (ch == '%') Y ch = '\n'; Y } Y Y if (!(escaped = (ch == '\\'))) { Y putc(ch, out); Y need_newline = (ch != '\n'); Y } Y } Y if (escaped) Y putc('\\', out); Y if (need_newline) Y putc('\n', out); Y Y /* close the pipe, causing an EOF condition. fclose causes Y * stdin_pipe[WRITE_PIPE] to be closed, too. Y */ Y fclose(out); Y Y Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) Y exit(0); Y } Y Y /* close the pipe to the grandkiddie's stdin, since its wicked uncle Y * ernie back there has it open and will close it when he's done. Y */ Y close(stdin_pipe[WRITE_PIPE]); Y Y children++; Y Y /* Y * read output from the grandchild. it's stderr has been redirected to Y * it's stdout, which has been redirected to our pipe. if there is any Y * output, we'll be mailing it to the user whose crontab this is... Y * when the grandchild exits, we'll get EOF. Y */ Y Y Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) Y Y /*local*/{ Y register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); Y register int ch = getc(in); Y Y if (ch != EOF) { Y register FILE *mail; Y register int bytes = 1; Y int status = 0; Y Y Debug(DPROC|DEXT, Y ("[%d] got data (%x:%c) from grandchild\n", Y getpid(), ch, ch)) Y Y /* get name of recipient. this is MAILTO if set to a Y * valid local username; USER otherwise. Y */ Y if (mailto) { Y /* MAILTO was present in the environment Y */ Y if (!*mailto) { Y /* ... but it's empty. set to NULL Y */ Y mailto = NULL; Y } Y } else { Y /* MAILTO not present, set to USER. Y */ Y mailto = usernm; Y } Y Y /* if we are supposed to be mailing, MAILTO will Y * be non-NULL. only in this case should we set Y * up the mail command and subjects and stuff... Y */ Y Y if (mailto) { Y register char **env; Y auto char mailcmd[MAX_COMMAND]; Y auto char hostname[MAXHOSTNAMELEN]; Y Y (void) gethostname(hostname, MAXHOSTNAMELEN); Y (void) sprintf(mailcmd, MAILARGS, Y MAILCMD, mailto); Y if (!(mail = cron_popen(mailcmd, "w"))) { Y perror(MAILCMD); Y (void) _exit(ERROR_EXIT); Y } Y fprintf(mail, "From: root (Cron Daemon)\n"); Y fprintf(mail, "To: %s\n", mailto); Y fprintf(mail, "Subject: Cron <%s@%s> %s\n", Y usernm, first_word(hostname, "."), Y e->cmd); Y# if defined(MAIL_DATE) Y fprintf(mail, "Date: %s\n", Y arpadate(&TargetTime)); Y# endif /* MAIL_DATE */ Y for (env = e->envp; *env; env++) Y fprintf(mail, "X-Cron-Env: <%s>\n", Y *env); Y fprintf(mail, "\n"); Y Y /* this was the first char from the pipe Y */ Y putc(ch, mail); Y } Y Y /* we have to read the input pipe no matter whether Y * we mail or not, but obviously we only write to Y * mail pipe if we ARE mailing. Y */ Y Y while (EOF != (ch = getc(in))) { Y bytes++; Y if (mailto) Y putc(ch, mail); Y } Y Y /* only close pipe if we opened it -- i.e., we're Y * mailing... Y */ Y Y if (mailto) { Y Debug(DPROC, ("[%d] closing pipe to mail\n", Y getpid())) Y /* Note: the pclose will probably see Y * the termination of the grandchild Y * in addition to the mail process, since Y * it (the grandchild) is likely to exit Y * after closing its stdout. Y */ Y status = cron_pclose(mail); Y } Y Y /* if there was output and we could not mail it, Y * log the facts so the poor user can figure out Y * what's going on. Y */ Y if (mailto && status) { Y char buf[MAX_TEMPSTR]; Y Y sprintf(buf, Y "mailed %d byte%s of output but got status 0x%04x\n", Y bytes, (bytes==1)?"":"s", Y status); Y log_it(usernm, getpid(), "MAIL", buf); Y } Y Y } /*if data from grandchild*/ Y Y Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) Y Y fclose(in); /* also closes stdout_pipe[READ_PIPE] */ Y } Y Y /* wait for children to die. Y */ Y for (; children > 0; children--) Y { Y WAIT_T waiter; Y PID_T pid; Y Y Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", Y getpid(), children)) Y pid = wait(&waiter); Y if (pid < OK) { Y Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", Y getpid())) Y break; Y } Y Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", Y getpid(), pid, WEXITSTATUS(waiter))) Y if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) Y Debug(DPROC, (", dumped core")) Y Debug(DPROC, ("\n")) Y } Y} SHAR_EOF chmod 644 'cron/do_command.c' fi if test -f 'cron/entry.c' then echo shar: "will not over-write existing file 'cron/entry.c'" else sed 's/^Y//' << \SHAR_EOF > 'cron/entry.c' Y/* Copyright 1988,1990,1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y#if !defined(lint) && !defined(LINT) Ystatic char rcsid[] = "$Id: entry.c,v 2.12 1994/01/17 03:20:37 vixie Exp $"; Y#endif Y Y/* vix 26jan87 [RCS'd; rest of log is in RCS file] Y * vix 01jan87 [added line-level error recovery] Y * vix 31dec86 [added /step to the from-to range, per bob@acornrc] Y * vix 30dec86 [written] Y */ Y Y Y#include "cron.h" Y Y Ytypedef enum ecode { Y e_none, e_minute, e_hour, e_dom, e_month, e_dow, Y e_cmd, e_timespec, e_username Y} ecode_e; Y Ystatic char get_list __P((bitstr_t *, int, int, char *[], int, FILE *)), Y get_range __P((bitstr_t *, int, int, char *[], int, FILE *)), Y get_number __P((int *, int, char *[], int, FILE *)); Ystatic int set_element __P((bitstr_t *, int, int, int)); Y Ystatic char *ecodes[] = Y { Y "no error", Y "bad minute", Y "bad hour", Y "bad day-of-month", Y "bad month", Y "bad day-of-week", Y "bad command", Y "bad time specifier", Y "bad username", Y }; Y Y Yvoid Yfree_entry(e) Y register entry *e; Y{ Y free(e->cmd); Y env_free(e->envp); Y free(e); Y} Y Y Y/* return NULL if eof or syntax error occurs; Y * otherwise return a pointer to a new entry. Y */ Yentry * Yload_entry(file, error_func, pw, envp) Y FILE *file; Y void (*error_func)(); Y register struct passwd *pw; Y char **envp; Y{ Y /* this function reads one crontab entry -- the next -- from a file. Y * it skips any leading blank lines, ignores comments, and returns Y * EOF if for any reason the entry can't be read and parsed. Y * Y * the entry is also parsed here. Y * Y * syntax: Y * user crontab: Y * minutes hours doms months dows cmd\n Y * system crontab (/etc/crontab): Y * minutes hours doms months dows USERNAME cmd\n Y */ Y Y ecode_e ecode = e_none; Y register entry *e; Y int ch; Y char cmd[MAX_COMMAND]; Y char envstr[MAX_ENVSTR]; Y Y Debug(DPARS, ("load_entry()...about to eat comments\n")) Y Y skip_comments(file); Y Y ch = get_char(file); Y if (ch == EOF) Y return NULL; Y Y /* ch is now the first useful character of a useful line. Y * it may be an @special or it may be the first character Y * of a list of minutes. Y */ Y Y e = (entry *) calloc(sizeof(entry), sizeof(char)); Y Y if (ch == '@') { Y /* all of these should be flagged and load-limited; i.e., Y * instead of @hourly meaning "0 * * * *" it should mean Y * "close to the front of every hour but not 'til the Y * system load is low". Problems are: how do you know Y * what "low" means? (save me from /etc/cron.conf!) and: Y * how to guarantee low variance (how low is low?), which Y * means how to we run roughly every hour -- seems like Y * we need to keep a history or let the first hour set Y * the schedule, which means we aren't load-limited Y * anymore. too much for my overloaded brain. (vix, jan90) Y * HINT Y */ Y ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); Y if (!strcmp("reboot", cmd)) { Y e->flags |= WHEN_REBOOT; Y } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ Y bit_set(e->minute, 0); Y bit_set(e->hour, 0); Y bit_set(e->dom, 0); Y bit_set(e->month, 0); Y bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); Y } else if (!strcmp("monthly", cmd)) { Y bit_set(e->minute, 0); Y bit_set(e->hour, 0); Y bit_set(e->dom, 0); Y bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); Y bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); Y } else if (!strcmp("weekly", cmd)) { Y bit_set(e->minute, 0); Y bit_set(e->hour, 0); Y bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); Y bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); Y bit_set(e->dow, 0); Y } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { Y bit_set(e->minute, 0); Y bit_set(e->hour, 0); Y bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); Y bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); Y bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); Y } else if (!strcmp("hourly", cmd)) { Y bit_set(e->minute, 0); Y bit_set(e->hour, (LAST_HOUR-FIRST_HOUR+1)); Y bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); Y bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); Y bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); Y } else { Y ecode = e_timespec; Y goto eof; Y } Y } else { Y Debug(DPARS, ("load_entry()...about to parse numerics\n")) Y Y ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, Y PPC_NULL, ch, file); Y if (ch == EOF) { Y ecode = e_minute; Y goto eof; Y } Y Y /* hours Y */ Y Y ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, Y PPC_NULL, ch, file); Y if (ch == EOF) { Y ecode = e_hour; Y goto eof; Y } Y Y /* DOM (days of month) Y */ Y Y if (ch == '*') Y e->flags |= DOM_STAR; Y ch = get_list(e->dom, FIRST_DOM, LAST_DOM, Y PPC_NULL, ch, file); Y if (ch == EOF) { Y ecode = e_dom; Y goto eof; Y } Y Y /* month Y */ Y Y ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, Y MonthNames, ch, file); Y if (ch == EOF) { Y ecode = e_month; Y goto eof; Y } Y Y /* DOW (days of week) Y */ Y Y if (ch == '*') Y e->flags |= DOW_STAR; Y ch = get_list(e->dow, FIRST_DOW, LAST_DOW, Y DowNames, ch, file); Y if (ch == EOF) { Y ecode = e_dow; Y goto eof; Y } Y } Y Y /* make sundays equivilent */ Y if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { Y bit_set(e->dow, 0); Y bit_set(e->dow, 7); Y } Y Y /* ch is the first character of a command, or a username */ Y unget_char(ch, file); Y Y if (!pw) { Y char *username = cmd; /* temp buffer */ Y Y Debug(DPARS, ("load_entry()...about to parse username\n")) Y ch = get_string(username, MAX_COMMAND, file, " \t"); Y Y Debug(DPARS, ("load_entry()...got %s\n",username)) Y if (ch == EOF) { Y ecode = e_cmd; Y goto eof; Y } Y Y pw = getpwnam(username); Y if (pw == NULL) { Y ecode = e_username; Y goto eof; Y } Y Debug(DPARS, ("load_entry()...uid %d, gid %d\n",e->uid,e->gid)) Y } Y Y e->uid = pw->pw_uid; Y e->gid = pw->pw_gid; Y Y /* copy and fix up environment. some variables are just defaults and Y * others are overrides. Y */ Y e->envp = env_copy(envp); Y if (!env_get("SHELL", e->envp)) { Y sprintf(envstr, "SHELL=%s", _PATH_BSHELL); Y e->envp = env_set(e->envp, envstr); Y } Y if (!env_get("HOME", e->envp)) { Y sprintf(envstr, "HOME=%s", pw->pw_dir); Y e->envp = env_set(e->envp, envstr); Y } Y if (!env_get("PATH", e->envp)) { Y sprintf(envstr, "PATH=%s", _PATH_DEFPATH); Y e->envp = env_set(e->envp, envstr); Y } Y sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name); Y e->envp = env_set(e->envp, envstr); Y sprintf(envstr, "%s=%s", "USER", pw->pw_name); Y e->envp = env_set(e->envp, envstr); Y Y Debug(DPARS, ("load_entry()...about to parse command\n")) Y Y /* Everything up to the next \n or EOF is part of the command... Y * too bad we don't know in advance how long it will be, since we Y * need to malloc a string for it... so, we limit it to MAX_COMMAND. Y * XXX - should use realloc(). Y */ Y ch = get_string(cmd, MAX_COMMAND, file, "\n"); Y Y /* a file without a \n before the EOF is rude, so we'll complain... Y */ Y if (ch == EOF) { Y ecode = e_cmd; Y goto eof; Y } Y Y /* got the command in the 'cmd' string; save it in *e. Y */ Y e->cmd = strdup(cmd); Y Y Debug(DPARS, ("load_entry()...returning successfully\n")) Y Y /* success, fini, return pointer to the entry we just created... Y */ Y return e; Y Y eof: Y free(e); Y if (ecode != e_none && error_func) Y (*error_func)(ecodes[(int)ecode]); Y while (ch != EOF && ch != '\n') Y ch = get_char(file); Y return NULL; Y} Y Y Ystatic char Yget_list(bits, low, high, names, ch, file) Y bitstr_t *bits; /* one bit per flag, default=FALSE */ Y int low, high; /* bounds, impl. offset for bitstr */ Y char *names[]; /* NULL or *[] of names for these elements */ Y int ch; /* current character being processed */ Y register FILE *file; /* file being read */ Y{ Y register int done; Y Y /* we know that we point to a non-blank character here; Y * must do a Skip_Blanks before we exit, so that the Y * next call (or the code that picks up the cmd) can Y * assume the same thing. Y */ Y Y Debug(DPARS|DEXT, ("get_list()...entered\n")) Y Y /* list = range {"," range} Y */ Y Y /* clear the bit string, since the default is 'off'. Y */ Y bit_nclear(bits, 0, (high-low+1)); Y Y /* process all ranges Y */ Y done = FALSE; Y while (!done) { Y ch = get_range(bits, low, high, names, ch, file); Y if (ch == ',') Y ch = get_char(file); Y else Y done = TRUE; Y } Y Y /* exiting. skip to some blanks, then skip over the blanks. Y */ Y Skip_Nonblanks(ch, file) Y Skip_Blanks(ch, file) Y Y Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch)) Y Y return ch; Y} Y Y Ystatic char Yget_range(bits, low, high, names, ch, file) Y bitstr_t *bits; /* one bit per flag, default=FALSE */ Y int low, high; /* bounds, impl. offset for bitstr */ Y char *names[]; /* NULL or names of elements */ Y register int ch; /* current character being processed */ Y FILE *file; /* file being read */ Y{ Y /* range = number | number "-" number [ "/" number ] Y */ Y Y register int i; Y auto int num1, num2, num3; Y Y Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")) Y Y if (ch == '*') { Y /* '*' means "first-last" but can still be modified by /step Y */ Y num1 = low; Y num2 = high; Y ch = get_char(file); Y if (ch == EOF) Y return EOF; Y } else { Y if (EOF == (ch = get_number(&num1, low, names, ch, file))) Y return EOF; Y Y if (ch != '-') { Y /* not a range, it's a single number. Y */ Y if (EOF == set_element(bits, low, high, num1)) Y return EOF; Y return ch; Y } else { Y /* eat the dash Y */ Y ch = get_char(file); Y if (ch == EOF) Y return EOF; Y Y /* get the number following the dash Y */ Y ch = get_number(&num2, low, names, ch, file); Y if (ch == EOF) Y return EOF; Y } Y } Y Y /* check for step size Y */ Y if (ch == '/') { Y /* eat the slash Y */ Y ch = get_char(file); Y if (ch == EOF) Y return EOF; Y Y /* get the step size -- note: we don't pass the Y * names here, because the number is not an Y * element id, it's a step size. 'low' is Y * sent as a 0 since there is no offset either. Y */ Y ch = get_number(&num3, 0, PPC_NULL, ch, file); Y if (ch == EOF) Y return EOF; Y } else { Y /* no step. default==1. Y */ Y num3 = 1; Y } Y Y /* range. set all elements from num1 to num2, stepping Y * by num3. (the step is a downward-compatible extension Y * proposed conceptually by bob@acornrc, syntactically Y * designed then implmented by paul vixie). Y */ Y for (i = num1; i <= num2; i += num3) Y if (EOF == set_element(bits, low, high, i)) Y return EOF; Y Y return ch; Y} Y Y Ystatic char Yget_number(numptr, low, names, ch, file) Y int *numptr; /* where does the result go? */ Y int low; /* offset applied to result if symbolic enum used */ Y char *names[]; /* symbolic names, if any, for enums */ Y register int ch; /* current character */ Y FILE *file; /* source */ Y{ Y char temp[MAX_TEMPSTR], *pc; Y int len, i, all_digits; Y Y /* collect alphanumerics into our fixed-size temp array Y */ Y pc = temp; Y len = 0; Y all_digits = TRUE; Y while (isalnum(ch)) { Y if (++len >= MAX_TEMPSTR) Y return EOF; Y Y *pc++ = ch; Y Y if (!isdigit(ch)) Y all_digits = FALSE; Y Y ch = get_char(file); Y } Y *pc = '\0'; Y Y /* try to find the name in the name list Y */ Y if (names) { Y for (i = 0; names[i] != NULL; i++) { Y Debug(DPARS|DEXT, Y ("get_num, compare(%s,%s)\n", names[i], temp)) Y if (!strcasecmp(names[i], temp)) { Y *numptr = i+low; Y return ch; Y } Y } Y } Y Y /* no name list specified, or there is one and our string isn't Y * in it. either way: if it's all digits, use its magnitude. Y * otherwise, it's an error. Y */ Y if (all_digits) { Y *numptr = atoi(temp); Y return ch; Y } Y Y return EOF; Y} Y Y Ystatic int Yset_element(bits, low, high, number) Y bitstr_t *bits; /* one bit per flag, default=FALSE */ Y int low; Y int high; Y int number; Y{ Y Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)) Y Y if (number < low || number > high) Y return EOF; Y Y bit_set(bits, (number-low)); Y return OK; Y} SHAR_EOF chmod 644 'cron/entry.c' fi if test -f 'cron/env.c' then echo shar: "will not over-write existing file 'cron/env.c'" else sed 's/^Y//' << \SHAR_EOF > 'cron/env.c' Y/* Copyright 1988,1990,1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y#if !defined(lint) && !defined(LINT) Ystatic char rcsid[] = "$Id: env.c,v 2.7 1994/01/26 02:25:50 vixie Exp vixie $"; Y#endif Y Y Y#include "cron.h" Y Y Ychar ** Yenv_init() Y{ Y register char **p = (char **) malloc(sizeof(char **)); Y Y p[0] = NULL; Y return (p); Y} Y Y Yvoid Yenv_free(envp) Y char **envp; Y{ Y register char **p; Y Y for (p = envp; *p; p++) Y free(*p); Y free(envp); Y} Y Y Ychar ** Yenv_copy(envp) Y char **envp; Y{ Y register int count, i; Y register char **p; Y Y for (count = 0; envp[count] != NULL; count++) Y ; Y p = (char **) malloc((count+1) * sizeof(char *)); /* 1 for the NULL */ Y for (i = 0; i < count; i++) Y p[i] = strdup(envp[i]); Y p[count] = NULL; Y return (p); Y} Y Y Ychar ** Yenv_set(envp, envstr) Y char **envp; Y char *envstr; Y{ Y register int count, found; Y register char **p; Y Y /* Y * count the number of elements, including the null pointer; Y * also set 'found' to -1 or index of entry if already in here. Y */ Y found = -1; Y for (count = 0; envp[count] != NULL; count++) { Y if (!strcmp_until(envp[count], envstr, '=')) Y found = count; Y } Y count++; /* for the NULL */ Y Y if (found != -1) { Y /* Y * it exists already, so just free the existing setting, Y * save our new one there, and return the existing array. Y */ Y free(envp[found]); Y envp[found] = strdup(envstr); Y return (envp); Y } Y Y /* Y * it doesn't exist yet, so resize the array, move null pointer over Y * one, save our string over the old null pointer, and return resized Y * array. Y */ Y p = (char **) realloc((void *) envp, Y (unsigned) ((count+1) * sizeof(char **))); Y p[count] = p[count-1]; Y p[count-1] = strdup(envstr); Y return (p); Y} Y Y Y/* return ERR = end of file Y * FALSE = not an env setting (file was repositioned) Y * TRUE = was an env setting Y */ Yint Yload_env(envstr, f) Y char *envstr; Y FILE *f; Y{ Y long filepos; Y int fileline; Y char name[MAX_TEMPSTR], val[MAX_ENVSTR]; Y int fields; Y Y filepos = ftell(f); Y fileline = LineNumber; Y skip_comments(f); Y if (EOF == get_string(envstr, MAX_ENVSTR, f, "\n")) Y return (ERR); Y Y Debug(DPARS, ("load_env, read <%s>\n", envstr)) Y Y name[0] = val[0] = '\0'; Y fields = sscanf(envstr, "%[^ =] = %[^\n#]", name, val); Y if (fields != 2) { Y Debug(DPARS, ("load_env, not 2 fields (%d)\n", fields)) Y fseek(f, filepos, 0); Y Set_LineNum(fileline); Y return (FALSE); Y } Y Y /* 2 fields from scanf; looks like an env setting Y */ Y Y /* Y * process value string Y */ Y /*local*/{ Y int len = strdtb(val); Y Y if (len >= 2) { Y if (val[0] == '\'' || val[0] == '"') { Y if (val[len-1] == val[0]) { Y val[len-1] = '\0'; Y (void) strcpy(val, val+1); Y } Y } Y } Y } Y Y (void) sprintf(envstr, "%s=%s", name, val); Y Debug(DPARS, ("load_env, <%s> <%s> -> <%s>\n", name, val, envstr)) Y return (TRUE); Y} Y Y Ychar * Yenv_get(name, envp) Y char *name; Y char **envp; Y{ Y register int len = strlen(name); Y register char *p, *q; Y Y while (p = *envp++) { Y if (!(q = strchr(p, '='))) Y continue; Y if ((q - p) == len && !strncmp(p, name, len)) Y return (q+1); Y } Y return (NULL); Y} SHAR_EOF chmod 644 'cron/env.c' fi if test -f 'cron/externs.h' then echo shar: "will not over-write existing file 'cron/externs.h'" else sed 's/^Y//' << \SHAR_EOF > 'cron/externs.h' Y/* Copyright 1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y#include Y#include Y#include Y#include Y Y#define DIR_T struct direct Y#define WEXITSTATUS(x) ((x).w_retcode) Y#define WTERMSIG(x) ((x).w_termsig) Y#define WCOREDUMP(x) ((x).w_coredump) Y Yextern int setsid __P((void)); SHAR_EOF chmod 644 'cron/externs.h' fi if test -f 'cron/job.c' then echo shar: "will not over-write existing file 'cron/job.c'" else sed 's/^Y//' << \SHAR_EOF > 'cron/job.c' Y/* Copyright 1988,1990,1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y#if !defined(lint) && !defined(LINT) Ystatic char rcsid[] = "$Id: job.c,v 1.6 1994/01/15 20:43:43 vixie Exp $"; Y#endif Y Y Y#include "cron.h" Y Y Ytypedef struct _job { Y struct _job *next; Y entry *e; Y user *u; Y} job; Y Y Ystatic job *jhead = NULL, *jtail = NULL; Y Y Yvoid Yjob_add(e, u) Y register entry *e; Y register user *u; Y{ Y register job *j; Y Y /* if already on queue, keep going */ Y for (j=jhead; j; j=j->next) Y if (j->e == e && j->u == u) { return; } Y Y /* build a job queue element */ Y j = (job*)malloc(sizeof(job)); Y j->next = (job*) NULL; Y j->e = e; Y j->u = u; Y Y /* add it to the tail */ Y if (!jhead) { jhead=j; } Y else { jtail->next=j; } Y jtail = j; Y} Y Y Yint Yjob_runqueue() Y{ Y register job *j, *jn; Y register int run = 0; Y Y for (j=jhead; j; j=jn) { Y do_command(j->e, j->u); Y jn = j->next; Y free(j); Y run++; Y } Y jhead = jtail = NULL; Y return run; Y} SHAR_EOF chmod 644 'cron/job.c' fi if test -f 'cron/misc.c' then echo shar: "will not over-write existing file 'cron/misc.c'" else sed 's/^Y//' << \SHAR_EOF > 'cron/misc.c' Y/* Copyright 1988,1990,1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y#if !defined(lint) && !defined(LINT) Ystatic char rcsid[] = "$Id: misc.c,v 2.9 1994/01/15 20:43:43 vixie Exp $"; Y#endif Y Y/* vix 26jan87 [RCS has the rest of the log] Y * vix 30dec86 [written] Y */ Y Y Y#include "cron.h" Y#include Y#include Y#include Y#include Y#include Y#include Y#if defined(SYSLOG) Y# include Y#endif Y Y Y#if defined(LOG_DAEMON) && !defined(LOG_CRON) Y#define LOG_CRON LOG_DAEMON Y#endif Y Ystatic int LogFD = ERR; Y Yint Ystrcmp_until(left, right, until) Y register char *left; Y register char *right; Y int until; Y{ Y register int diff; Y Y while (*left && *left != until && *left == *right) { Y left++; Y right++; Y } Y Y if ((*left=='\0' || *left == until) && Y (*right=='\0' || *right == until)) { Y diff = 0; Y } else { Y diff = *left - *right; Y } Y Y return diff; Y} Y Y Y/* strdtb(s) - delete trailing blanks in string 's' and return new length Y */ Yint Ystrdtb(s) Y register char *s; Y{ Y register char *x = s; Y Y /* scan forward to the null Y */ Y while (*x) Y x++; Y Y /* scan backward to either the first character before the string, Y * or the last non-blank in the string, whichever comes first. Y */ Y do {x--;} Y while (x >= s && isspace(*x)); Y Y /* one character beyond where we stopped above is where the null Y * goes. Y */ Y *++x = '\0'; Y Y /* the difference between the position of the null character and Y * the position of the first character of the string is the length. Y */ Y return x - s; Y} Y Y Yint Yset_debug_flags(flags) Y char *flags; Y{ Y /* debug flags are of the form flag[,flag ...] Y * Y * if an error occurs, print a message to stdout and return FALSE. Y * otherwise return TRUE after setting ERROR_FLAGS. Y */ Y Y#if !DEBUGGING Y Y printf("this program was compiled without debugging enabled\n"); Y return FALSE; Y Y#else /* DEBUGGING */ Y Y register char *pc = flags; Y Y DebugFlags = 0; Y Y while (*pc) { Y char **test; Y int mask; Y Y /* try to find debug flag name in our list. Y */ Y for ( test = DebugFlagNames, mask = 1; Y *test && strcmp_until(*test, pc, ','); Y test++, mask <<= 1 Y ) Y ; Y Y if (!*test) { Y fprintf(stderr, Y "unrecognized debug flag <%s> <%s>\n", Y flags, pc); Y return FALSE; Y } Y Y DebugFlags |= mask; Y Y /* skip to the next flag Y */ Y while (*pc && *pc != ',') Y pc++; Y if (*pc == ',') Y pc++; Y } Y Y if (DebugFlags) { Y int flag; Y Y fprintf(stderr, "debug flags enabled:"); Y Y for (flag = 0; DebugFlagNames[flag]; flag++) Y if (DebugFlags & (1 << flag)) Y fprintf(stderr, " %s", DebugFlagNames[flag]); Y fprintf(stderr, "\n"); Y } Y Y return TRUE; Y Y#endif /* DEBUGGING */ Y} Y Y Yvoid Yset_cron_uid() Y{ Y if (seteuid(ROOT_UID) < OK) { Y perror("seteuid"); Y exit(ERROR_EXIT); Y } Y} Y Y Yvoid Yset_cron_cwd() Y{ Y struct stat sb; Y Y /* first check for CRONDIR ("/var/cron" or some such) Y */ Y if (stat(CRONDIR, &sb) < OK && errno == ENOENT) { Y perror(CRONDIR); Y if (OK == mkdir(CRONDIR, 0700)) { Y fprintf(stderr, "%s: created\n", CRONDIR); Y stat(CRONDIR, &sb); Y } else { Y fprintf(stderr, "%s: ", CRONDIR); Y perror("mkdir"); Y exit(ERROR_EXIT); Y } Y } Y if (!(sb.st_mode & S_IFDIR)) { Y fprintf(stderr, "'%s' is not a directory, bailing out.\n", Y CRONDIR); Y exit(ERROR_EXIT); Y } Y if (chdir(CRONDIR) < OK) { Y fprintf(stderr, "cannot chdir(%s), bailing out.\n", CRONDIR); Y perror(CRONDIR); Y exit(ERROR_EXIT); Y } Y Y /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such) Y */ Y if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) { Y perror(SPOOL_DIR); Y if (OK == mkdir(SPOOL_DIR, 0700)) { Y fprintf(stderr, "%s: created\n", SPOOL_DIR); Y stat(SPOOL_DIR, &sb); Y } else { Y fprintf(stderr, "%s: ", SPOOL_DIR); Y perror("mkdir"); Y exit(ERROR_EXIT); Y } Y } Y if (!(sb.st_mode & S_IFDIR)) { Y fprintf(stderr, "'%s' is not a directory, bailing out.\n", Y SPOOL_DIR); Y exit(ERROR_EXIT); Y } Y} Y Y Y/* acquire_daemonlock() - write our PID into /etc/cron.pid, unless Y * another daemon is already running, which we detect here. Y * Y * note: main() calls us twice; once before forking, once after. Y * we maintain static storage of the file pointer so that we Y * can rewrite our PID into the PIDFILE after the fork. Y * Y * it would be great if fflush() disassociated the file buffer. Y */ Yvoid Yacquire_daemonlock(closeflag) Y int closeflag; Y{ Y static FILE *fp = NULL; Y Y if (closeflag && fp) { Y fclose(fp); Y fp = NULL; Y return; Y } Y Y if (!fp) { Y char pidfile[MAX_FNAME]; Y char buf[MAX_TEMPSTR]; Y int fd, otherpid; Y Y (void) sprintf(pidfile, PIDFILE, PIDDIR); Y if ((-1 == (fd = open(pidfile, O_RDWR|O_CREAT, 0644))) Y || (NULL == (fp = fdopen(fd, "r+"))) Y ) { Y sprintf(buf, "can't open or create %s: %s", Y pidfile, strerror(errno)); Y fprintf(stderr, "%s: %s\n", ProgramName, buf); Y log_it("CRON", getpid(), "DEATH", buf); Y exit(ERROR_EXIT); Y } Y Y if (flock(fd, LOCK_EX|LOCK_NB) < OK) { Y int save_errno = errno; Y Y fscanf(fp, "%d", &otherpid); Y sprintf(buf, "can't lock %s, otherpid may be %d: %s", Y pidfile, otherpid, strerror(save_errno)); Y fprintf(stderr, "%s: %s\n", ProgramName, buf); Y log_it("CRON", getpid(), "DEATH", buf); Y exit(ERROR_EXIT); Y } Y Y (void) fcntl(fd, F_SETFD, 1); Y } Y Y rewind(fp); Y fprintf(fp, "%d\n", getpid()); Y fflush(fp); Y (void) ftruncate(fileno(fp), ftell(fp)); Y Y /* abandon fd and fp even though the file is open. we need to Y * keep it open and locked, but we don't need the handles elsewhere. Y */ Y} Y Y/* get_char(file) : like getc() but increment LineNumber on newlines Y */ Yint Yget_char(file) Y register FILE *file; Y{ Y register int ch; Y Y ch = getc(file); Y if (ch == '\n') Y Set_LineNum(LineNumber + 1) Y return ch; Y} Y Y Y/* unget_char(ch, file) : like ungetc but do LineNumber processing Y */ Yvoid Yunget_char(ch, file) Y register int ch; Y register FILE *file; Y{ Y ungetc(ch, file); Y if (ch == '\n') Y Set_LineNum(LineNumber - 1) Y} Y Y Y/* get_string(str, max, file, termstr) : like fgets() but Y * (1) has terminator string which should include \n Y * (2) will always leave room for the null Y * (3) uses get_char() so LineNumber will be accurate Y * (4) returns EOF or terminating character, whichever Y */ Yint Yget_string(string, size, file, terms) Y register char *string; Y int size; Y FILE *file; Y char *terms; Y{ Y register int ch; Y Y while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) { Y if (size > 1) { Y *string++ = (char) ch; Y size--; Y } Y } Y Y if (size > 0) Y *string = '\0'; Y Y return ch; Y} Y Y Y/* skip_comments(file) : read past comment (if any) Y */ Yvoid Yskip_comments(file) Y register FILE *file; Y{ Y register int ch; Y Y while (EOF != (ch = get_char(file))) { Y /* ch is now the first character of a line. Y */ Y Y while (ch == ' ' || ch == '\t') Y ch = get_char(file); Y Y if (ch == EOF) Y break; Y Y /* ch is now the first non-blank character of a line. Y */ Y Y if (ch != '\n' && ch != '#') Y break; Y Y /* ch must be a newline or comment as first non-blank Y * character on a line. Y */ Y Y while (ch != '\n' && ch != EOF) Y ch = get_char(file); Y Y /* ch is now the newline of a line which we're going to Y * ignore. Y */ Y } Y if (ch != EOF) Y unget_char(ch, file); Y} Y Y Y/* int in_file(char *string, FILE *file) Y * return TRUE if one of the lines in file matches string exactly, Y * FALSE otherwise. Y */ Ystatic int Yin_file(string, file) Y char *string; Y FILE *file; Y{ Y char line[MAX_TEMPSTR]; Y Y rewind(file); Y while (fgets(line, MAX_TEMPSTR, file)) { Y if (line[0] != '\0') Y line[strlen(line)-1] = '\0'; Y if (0 == strcmp(line, string)) Y return TRUE; Y } Y return FALSE; Y} Y Y Y/* int allowed(char *username) Y * returns TRUE if (ALLOW_FILE exists and user is listed) Y * or (DENY_FILE exists and user is NOT listed) Y * or (neither file exists but user=="root" so it's okay) Y */ Yint Yallowed(username) Y char *username; Y{ Y static int init = FALSE; Y static FILE *allow, *deny; Y Y if (!init) { Y init = TRUE; Y#if defined(ALLOW_FILE) && defined(DENY_FILE) Y allow = fopen(ALLOW_FILE, "r"); Y deny = fopen(DENY_FILE, "r"); Y Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny)) Y#else Y allow = NULL; Y deny = NULL; Y#endif Y } Y Y if (allow) Y return (in_file(username, allow)); Y if (deny) Y return (!in_file(username, deny)); Y Y#if defined(ALLOW_ONLY_ROOT) Y return (strcmp(username, ROOT_USER) == 0); Y#else Y return TRUE; Y#endif Y} Y Y Yvoid Ylog_it(username, xpid, event, detail) Y char *username; Y int xpid; Y char *event; Y char *detail; Y{ Y PID_T pid = xpid; Y#if defined(LOG_FILE) Y char *msg; Y TIME_T now = time((TIME_T) 0); Y register struct tm *t = localtime(&now); Y#endif /*LOG_FILE*/ Y Y#if defined(SYSLOG) Y static int syslog_open = 0; Y#endif Y Y#if defined(LOG_FILE) Y /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation. Y */ Y msg = (char *)malloc(strlen(username) Y + strlen(event) Y + strlen(detail) Y + MAX_TEMPSTR); Y Y if (LogFD < OK) { Y LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600); Y if (LogFD < OK) { Y fprintf(stderr, "%s: can't open log file\n", Y ProgramName); Y perror(LOG_FILE); Y } else { Y (void) fcntl(LogFD, F_SETFD, 1); Y } Y } Y Y /* we have to sprintf() it because fprintf() doesn't always write Y * everything out in one chunk and this has to be atomically appended Y * to the log file. Y */ Y sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n", Y username, Y t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, pid, Y event, detail); Y Y /* we have to run strlen() because sprintf() returns (char*) on old BSD Y */ Y if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) { Y if (LogFD >= OK) Y perror(LOG_FILE); Y fprintf(stderr, "%s: can't write to log file\n", ProgramName); Y write(STDERR, msg, strlen(msg)); Y } Y Y free(msg); Y#endif /*LOG_FILE*/ Y Y#if defined(SYSLOG) Y if (!syslog_open) { Y /* we don't use LOG_PID since the pid passed to us by Y * our client may not be our own. therefore we want to Y * print the pid ourselves. Y */ Y openlog(ProgramName, LOG_PID, LOG_CRON); Y syslog_open = TRUE; /* assume openlog success */ Y } Y Y syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail); Y Y#endif /*SYSLOG*/ Y Y#if DEBUGGING Y if (DebugFlags) { Y fprintf(stderr, "log_it: (%s %d) %s (%s)\n", Y username, pid, event, detail); Y } Y#endif Y} Y Y Yvoid Ylog_close() { Y if (LogFD != ERR) { Y close(LogFD); Y LogFD = ERR; Y } Y} Y Y Y/* two warnings: Y * (1) this routine is fairly slow Y * (2) it returns a pointer to static storage Y */ Ychar * Yfirst_word(s, t) Y register char *s; /* string we want the first word of */ Y char *t; /* terminators, implicitly including \0 */ Y{ Y static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */ Y static int retsel = 0; Y register char *rb, *rp; Y Y /* select a return buffer */ Y retsel = 1-retsel; Y rb = &retbuf[retsel][0]; Y rp = rb; Y Y /* skip any leading terminators */ Y while (*s && (NULL != strchr(t, *s))) { Y s++; Y } Y Y /* copy until next terminator or full buffer */ Y while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) { Y *rp++ = *s++; Y } Y Y /* finish the return-string and return it */ Y *rp = '\0'; Y return rb; Y} Y Y Y/* warning: Y * heavily ascii-dependent. Y */ Yvoid Ymkprint(dst, src, len) Y register char *dst; Y register unsigned char *src; Y register int len; Y{ Y while (len-- > 0) Y { Y register unsigned char ch = *src++; Y Y if (ch < ' ') { /* control character */ Y *dst++ = '^'; Y *dst++ = ch + '@'; Y } else if (ch < 0177) { /* printable */ Y *dst++ = ch; Y } else if (ch == 0177) { /* delete/rubout */ Y *dst++ = '^'; Y *dst++ = '?'; Y } else { /* parity character */ Y sprintf(dst, "\\%03o", ch); Y dst += 4; Y } Y } Y *dst = '\0'; Y} Y Y Y/* warning: Y * returns a pointer to malloc'd storage, you must call free yourself. Y */ Ychar * Ymkprints(src, len) Y unsigned char *src; Y unsigned int len; Y{ Y register char *dst = (char *)malloc(len*4 + 1); Y Y mkprint(dst, src, len); Y Y return dst; Y} Y Y Y#ifdef MAIL_DATE Y/* Sat, 27 Feb 93 11:44:51 CST Y * 123456789012345678901234567 Y */ Ychar * Yarpadate(clock) Y time_t *clock; Y{ Y time_t t = clock ?*clock :time(0L); Y struct tm *tm = localtime(&t); Y static char ret[30]; /* zone name might be >3 chars */ Y Y (void) sprintf(ret, "%s, %2d %s %2d %02d:%02d:%02d %s", Y DowNames[tm->tm_wday], Y tm->tm_mday, Y MonthNames[tm->tm_mon], Y tm->tm_year, Y tm->tm_hour, Y tm->tm_min, Y tm->tm_sec, Y TZONE(*tm)); Y return ret; Y} Y#endif /*MAIL_DATE*/ Y Ystatic int save_euid; Yint swap_uids() Y { Y save_euid = geteuid(); Y return(seteuid(getuid())); Y } Y Yint swap_uids_back() Y { Y return(seteuid(save_euid)); Y } SHAR_EOF chmod 644 'cron/misc.c' fi if test -f 'cron/pathnames.h' then echo shar: "will not over-write existing file 'cron/pathnames.h'" else sed 's/^Y//' << \SHAR_EOF > 'cron/pathnames.h' Y/* Copyright 1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y/* Y * $Id: pathnames.h,v 1.3 1994/01/15 20:43:43 vixie Exp $ Y */ Y Y#include Y Y#ifndef CRONDIR Y /* CRONDIR is where crond(8) and crontab(1) both chdir Y * to; SPOOL_DIR, ALLOW_FILE, DENY_FILE, and LOG_FILE Y * are all relative to this directory. Y */ Y#define CRONDIR "/var/cron" Y#endif Y Y /* SPOOLDIR is where the crontabs live. Y * This directory will have its modtime updated Y * whenever crontab(1) changes a crontab; this is Y * the signal for crond(8) to look at each individual Y * crontab file and reload those whose modtimes are Y * newer than they were last time around (or which Y * didn't exist last time around...) Y */ Y#define SPOOL_DIR "tabs" Y Y /* undefining these turns off their features. note Y * that ALLOW_FILE and DENY_FILE must both be defined Y * in order to enable the allow/deny code. If neither Y * LOG_FILE or SYSLOG is defined, we don't log. If Y * both are defined, we log both ways. Y */ Y#define ALLOW_FILE "allow" /*-*/ Y#define DENY_FILE "deny" /*-*/ Y#undef LOG_FILE /* "log" */ Y Y /* where should the daemon stick its PID? Y */ Y#ifdef _PATH_VARRUN Y# define PIDDIR _PATH_VARRUN Y#else Y# define PIDDIR "/etc/" Y#endif Y#define PIDFILE "%scron.pid" Y Y /* 4.3BSD-style crontab */ Y#define SYSCRONTAB "/etc/crontab" Y Y /* what editor to use if no EDITOR or VISUAL Y * environment variable specified. Y */ Y#if defined(_PATH_VI) Y# define EDITOR _PATH_VI Y#else Y# define EDITOR "/usr/ucb/vi" Y#endif Y Y#ifndef _PATH_BSHELL Y# define _PATH_BSHELL "/bin/sh" Y#endif Y Y#ifndef _PATH_DEFPATH Y# define _PATH_DEFPATH "/usr/bin:/bin" Y#endif SHAR_EOF chmod 644 'cron/pathnames.h' fi if test -f 'cron/popen.c' then echo shar: "will not over-write existing file 'cron/popen.c'" else sed 's/^Y//' << \SHAR_EOF > 'cron/popen.c' Y/* Y * Copyright (c) 1988 The Regents of the University of California. Y * All rights reserved. Y * Y * This code is derived from software written by Ken Arnold and Y * published in UNIX Review, Vol. 6, No. 8. Y * Y * Redistribution and use in source and binary forms are permitted Y * provided that the above copyright notice and this paragraph are Y * duplicated in all such forms and that any documentation, Y * advertising materials, and other materials related to such Y * distribution and use acknowledge that the software was developed Y * by the University of California, Berkeley. The name of the Y * University may not be used to endorse or promote products derived Y * from this software without specific prior written permission. Y * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR Y * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED Y * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Y * Y */ Y Y/* this came out of the ftpd sources; it's been modified to avoid the Y * globbing stuff since we don't need it. also execvp instead of execv. Y */ Y Y#ifndef lint Ystatic char rcsid[] = "$Id: popen.c,v 1.5 1994/01/15 20:43:43 vixie Exp $"; Ystatic char sccsid[] = "@(#)popen.c 5.7 (Berkeley) 2/14/89"; Y#endif /* not lint */ Y Y#include "cron.h" Y#include Y Y Y#define WANT_GLOBBING 0 Y Y/* Y * Special version of popen which avoids call to shell. This insures noone Y * may create a pipe to a hidden program as a side effect of a list or dir Y * command. Y */ Ystatic PID_T *pids; Ystatic int fds; Y YFILE * Ycron_popen(program, type) Y char *program, *type; Y{ Y register char *cp; Y FILE *iop; Y int argc, pdes[2]; Y PID_T pid; Y char *argv[100]; Y#if WANT_GLOBBING Y char **pop, *vv[2]; Y int gargc; Y char *gargv[1000]; Y extern char **glob(), **copyblk(); Y#endif Y Y if (*type != 'r' && *type != 'w' || type[1]) Y return(NULL); Y Y if (!pids) { Y if ((fds = getdtablesize()) <= 0) Y return(NULL); Y if (!(pids = (PID_T *)malloc((u_int)(fds * sizeof(PID_T))))) Y return(NULL); Y bzero((char *)pids, fds * sizeof(PID_T)); Y } Y if (pipe(pdes) < 0) Y return(NULL); Y Y /* break up string into pieces */ Y for (argc = 0, cp = program;; cp = NULL) Y if (!(argv[argc++] = strtok(cp, " \t\n"))) Y break; Y Y#if WANT_GLOBBING Y /* glob each piece */ Y gargv[0] = argv[0]; Y for (gargc = argc = 1; argv[argc]; argc++) { Y if (!(pop = glob(argv[argc]))) { /* globbing failed */ Y vv[0] = argv[argc]; Y vv[1] = NULL; Y pop = copyblk(vv); Y } Y argv[argc] = (char *)pop; /* save to free later */ Y while (*pop && gargc < 1000) Y gargv[gargc++] = *pop++; Y } Y gargv[gargc] = NULL; Y#endif Y Y iop = NULL; Y switch(pid = vfork()) { Y case -1: /* error */ Y (void)close(pdes[0]); Y (void)close(pdes[1]); Y goto pfree; Y /* NOTREACHED */ Y case 0: /* child */ Y if (*type == 'r') { Y if (pdes[1] != 1) { Y dup2(pdes[1], 1); Y dup2(pdes[1], 2); /* stderr, too! */ Y (void)close(pdes[1]); Y } Y (void)close(pdes[0]); Y } else { Y if (pdes[0] != 0) { Y dup2(pdes[0], 0); Y (void)close(pdes[0]); Y } Y (void)close(pdes[1]); Y } Y#if WANT_GLOBBING Y execvp(gargv[0], gargv); Y#else Y execvp(argv[0], argv); Y#endif Y _exit(1); Y } Y /* parent; assume fdopen can't fail... */ Y if (*type == 'r') { Y iop = fdopen(pdes[0], type); Y (void)close(pdes[1]); Y } else { Y iop = fdopen(pdes[1], type); Y (void)close(pdes[0]); Y } Y pids[fileno(iop)] = pid; Y Ypfree: Y#if WANT_GLOBBING Y for (argc = 1; argv[argc] != NULL; argc++) { Y/* blkfree((char **)argv[argc]); */ Y free((char *)argv[argc]); Y } Y#endif Y return(iop); Y} Y Yint Ycron_pclose(iop) Y FILE *iop; Y{ Y register int fdes; Y int omask; Y WAIT_T stat_loc; Y PID_T pid; Y Y /* Y * pclose returns -1 if stream is not associated with a Y * `popened' command, or, if already `pclosed'. Y */ Y if (pids == 0 || pids[fdes = fileno(iop)] == 0) Y return(-1); Y (void)fclose(iop); Y omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP)); Y while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1) Y ; Y (void)sigsetmask(omask); Y pids[fdes] = 0; Y return (pid == -1 ? -1 : WEXITSTATUS(stat_loc)); Y} SHAR_EOF chmod 644 'cron/popen.c' fi if test -f 'cron/putman.sh' then echo shar: "will not over-write existing file 'cron/putman.sh'" else sed 's/^Y//' << \SHAR_EOF > 'cron/putman.sh' Y#!/bin/sh Y Y# putman.sh - install a man page according to local custom Y# vixie 27dec93 [original] Y# Y# $Id:$ Y YPAGE=$1 YDIR=$2 Y YSECT=`expr $PAGE : '[a-z]*.\([0-9]\)'` YMDIR="$DIR/cat$SECT" YDEST="$MDIR/`basename $PAGE .$SECT`.0" Y Yset -x Yif [ ! -d $MDIR ]; then Y rm -f $MDIR Y mkdir -p $MDIR Y chmod 755 $MDIR Yfi Y Ynroff -man $PAGE >$DEST Ychmod 444 $DEST Yset +x Y Yexit 0 SHAR_EOF chmod 644 'cron/putman.sh' fi if test -f 'cron/user.c' then echo shar: "will not over-write existing file 'cron/user.c'" else sed 's/^Y//' << \SHAR_EOF > 'cron/user.c' Y/* Copyright 1988,1990,1993,1994 by Paul Vixie Y * All rights reserved Y * Y * Distribute freely, except: don't remove my name from the source or Y * documentation (don't take credit for my work), mark your changes (don't Y * get me blamed for your possible bugs), don't alter or remove this Y * notice. May be sold if buildable source is provided to buyer. No Y * warrantee of any kind, express or implied, is included with this Y * software; use at your own risk, responsibility for damages (if any) to Y * anyone resulting from the use of this software rests entirely with the Y * user. Y * Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and Y * I'll try to keep a version up to date. I can be reached as follows: Y * Paul Vixie uunet!decwrl!vixie!paul Y */ Y Y#if !defined(lint) && !defined(LINT) Ystatic char rcsid[] = "$Id: user.c,v 2.8 1994/01/15 20:43:43 vixie Exp $"; Y#endif Y Y/* vix 26jan87 [log is in RCS file] Y */ Y Y Y#include "cron.h" Y Y Yvoid Yfree_user(u) Y user *u; Y{ Y register entry *e, *ne; Y Y free(u->name); Y for (e = u->crontab; e != NULL; e = ne) { Y ne = e->next; Y free_entry(e); Y } Y free(u); Y} Y Y Yuser * Yload_user(crontab_fd, pw, name) Y int crontab_fd; Y struct passwd *pw; /* NULL implies syscrontab */ Y char *name; Y{ Y char envstr[MAX_ENVSTR]; Y FILE *file; Y register user *u; Y register entry *e; Y int status; Y char **envp; Y Y if (!(file = fdopen(crontab_fd, "r"))) { Y perror("fdopen on crontab_fd in load_user"); Y return NULL; Y } Y Y Debug(DPARS, ("load_user()\n")) Y Y /* file is open. build user entry, then read the crontab file. Y */ Y u = (user *) malloc(sizeof(user)); Y u->name = strdup(name); Y u->crontab = NULL; Y Y /* Y * init environment. this will be copied/augmented for each entry. Y */ Y envp = env_init(); Y Y /* Y * load the crontab Y */ Y while ((status = load_env(envstr, file)) >= OK) { Y switch (status) { Y case ERR: Y free_user(u); Y u = NULL; Y goto done; Y case FALSE: Y e = load_entry(file, NULL, pw, envp); Y if (e) { Y e->next = u->crontab; Y u->crontab = e; Y } Y break; Y case TRUE: Y envp = env_set(envp, envstr); Y break; Y } Y } Y Y done: Y env_free(envp); Y fclose(file); Y Debug(DPARS, ("...load_user() done\n")) Y return u; Y} SHAR_EOF chmod 644 'cron/user.c' fi exit 0 # End of shell archive