paul@vixie.UUCP (Paul Vixie Esq) (06/12/87)
#! /bin/sh ## This is a shell archive. Remove anything before this line, then unpack ## it by saving it into a file and typing "sh file". To overwrite existing ## files, type "sh file -c". You can also feed this as standard input via ## unshar, or by typing "sh <file". If this archive is complete, you will ## see the following message at the end: # "End of archive 2 (of 2)." # Contents: cron.h crontab.c do_command.c entry.c # Wrapped by paul@vixie on Wed May 6 10:18:56 1987 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f cron.h -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"cron.h\" else echo shar: Extracting \"cron.h\" \(7420 characters\) sed "s/^X//" >cron.h <<'END_OF_cron.h' X/* cron.h - header for vixie's cron X * X * $Header: cron.h,v 1.4 87/05/02 17:33:08 paul Exp $ X * $Source: /usr/src/local/vix/cron/cron.h,v $ X * $Revision: 1.4 $ X * $Log: cron.h,v $ X * Revision 1.4 87/05/02 17:33:08 paul X * baseline for mod.sources release X * X * Revision 1.3 87/03/31 12:07:13 paul X * more and more and more suggestions from rs@mirror X * X * Revision 1.2 87/02/13 00:29:14 paul X * getting ready for beta test X * X * Revision 1.1 87/02/13 00:26:01 paul X * Initial revision X * X * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley] X * vix 30dec86 [written] X */ X X/* Copyright 1987 by Vixie Enterprises X * All rights reserved X * X * Distribute freely, except: don't sell it, don't remove my name from the X * source or documentation (don't take credit for my work), mark your changes X * (don't get me blamed for your possible bugs), don't alter or remove this X * notice. Commercial redistribution is negotiable; contact me for details. X * X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and X * I'll try to keep a version up to date. I can be reached as follows: X * Paul Vixie, Vixie Enterprises, 329 Noe Street, San Francisco, CA, 94114, X * (415) 864-7013, {ucbvax!dual,ames,ucsfmis,lll-crg,sun}!ptsfa!vixie!paul. X */ X X#ifndef _CRON_FLAG X#define _CRON_FLAG X X#include <stdio.h> X#include <ctype.h> X#include <bitstring.h> X X/* X * these are site-dependent X */ X /* X * choose one of these MAILCMD commands. I use X * /bin/mail for speed; it makes 'biff' go but doesn't X * do aliasing. /usr/lib/sendmail does aliasing but is X * a hog for short messages. X */ X X/*# define MAILCMD "/usr/lib/sendmail -F\"Vcron Daemon\" -odi -oem %s" /*-*/ X /* -Fx = set full-name of sender X * -odi = Option Deliverymode Interactive (synchronous) X * -oem = Option Errors Mailedtosender X */ X X# define MAILCMD "/bin/mail -d %s" /*-*/ X /* -d = undocumented but common flag: deliver locally? X */ X X /* LIBDIR is unrelated to /usr/lib; it's actually X * the base of the cron spool tree. any control X * files for cron will be there; also a directory X * called 'crontabs' will be there to hold the X * individual cron tabs. this is less clean than X * a /usr/spool/cron, /usr/lib/cron; you can change X * it to be that way at your own taste. remember X * that SysV does it /usr/spool/cron, .../crontabs. X */ X#define LIBDIR "/usr/spool/cron/%s" X X /* SPOOLDIR is where the crontabs live X */ X#define SPOOLDIR "/usr/spool/cron/crontabs/%s" X X /* this file is created by 'crontab' when it changes X * a cron tab; 'cron' sees it and reloads its tables. X */ X#define POKECRON "/usr/spool/cron/POKECRON" X X /* this is the name of the environment variable X * that contains the user name. it isn't read by X * cron, but it is SET by crond in the environments X * it creates for subprocesses. on BSD, it will X * always be USER; on SysV it could be LOGNAME or X * something else. X */ X#define USERENV "USER" X X/* X * you shouldn't have to change anything after this X */ X X X /* these are really immutable, and are X * defined for symbolic convenience only X * TRUE, FALSE, and ERR must be distinct X */ X#define TRUE 1 X#define FALSE 0 X /* system calls return this on success */ X#define OK 0 X /* or this on error */ X#define ERR (-1) X X /* turn this on to get '-x' code */ X#define DEBUGGING FALSE X X#define READ_PIPE 0 /* which end of a pipe pair do you read? */ X#define WRITE_PIPE 1 /* or write to? */ X#define STDIN 0 /* what is stdin's file descriptor? */ X#define STDOUT 1 /* stdout's? */ X#define STDERR 2 /* stderr's? */ X#define VFORK vfork /* sometimes vfork might be too virtual */ X#define ERROR_EXIT 1 /* exit() with this will scare the shell */ X#define OK_EXIT 0 /* exit() with this is considered 'normal' */ X#define MAX_FNAME 100 /* max length of internally generated fn */ X#define MAX_COMMAND 1000 /* max length of internally generated cmd */ X#define MAX_TEMPSTR 100 /* obvious */ X#define MAX_UNAME 20 /* max length of username, should be overkill */ X#define ROOT_UID 0 /* don't change this, it really must be root */ X#define DEXT 0x0001 /* extend flag for other debug masks */ X#define DSCH 0x0002 /* scheduling debug mask */ X#define DPROC 0x0004 /* process control debug mask */ X#define DPARS 0x0008 /* parsing debug mask */ X#define DMISC 0x0010 /* misc debug flag */ X#define DCOUNT 5 /* how many debug bits are in use? */ X X#define MAILSUBJ "one of your cron commands generated this output" X#define REG register X#define PPC_NULL ((char **)NULL) X X#define Skip_Blanks(c, f) \ X while (c == '\t' || c == ' ') \ X c = get_char(f); X X#define Skip_Nonblanks(c, f) \ X while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \ X c = get_char(f); X X#define Skip_Line(c, f) \ X do {c = get_char(f);} while (c != '\n' && c != EOF); X X#if DEBUGGING X# define Debug(mask, message) \ X if ( (DEBUG_FLAGS & (mask) ) == (mask) ) \ X printf message; X#else /* !DEBUGGING */ X# define Debug(mask, message) \ X ; X#endif /* DEBUGGING */ X X#define MkLower(ch) (isupper(ch) ? tolower(ch) : ch) X#define MkUpper(ch) (islower(ch) ? toupper(ch) : ch) X#define Set_LineNum(ln) {Debug(DPARS|DEXT,("linenum=%d\n",ln)); \ X LINE_NUMBER = ln; \ X } X X#define FIRST_MINUTE 0 X#define LAST_MINUTE 59 X#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1) X X#define FIRST_HOUR 0 X#define LAST_HOUR 23 X#define HOUR_COUNT (LAST_HOUR - FIRST_HOUR + 1) X X#define FIRST_DOM 1 X#define LAST_DOM 31 X#define DOM_COUNT (LAST_DOM - FIRST_DOM + 1) X X#define FIRST_MONTH 1 X#define LAST_MONTH 12 X#define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1) X X#define FIRST_DOW 0 X#define LAST_DOW 7 X#define DOW_COUNT (LAST_DOW - FIRST_DOW + 1) X X /* each users' crontab will be held as a list of X * the following structure. X */ X Xtypedef struct _entry X { X struct _entry *next; X char *cmd; X bit_decl( minute, MINUTE_COUNT ) X bit_decl( hour, HOUR_COUNT ) X bit_decl( dom, DOM_COUNT ) X bit_decl( month, MONTH_COUNT ) X bit_decl( dow, DOW_COUNT ) X int dom_star, dow_star; X } X entry; X X /* the crontab database will be a list of the X * following structure, one element per user. X */ X Xtypedef struct _user X { X struct _user *next; /* next item in list */ X int uid; /* uid from passwd file */ X int gid; /* gid from passwd file */ X char **envp; /* environ for commands */ X entry *crontab; /* this person's crontab */ X } X user; X X /* in the C tradition, we only create X * variables for the main program, just X * extern them elsewhere. X */ X X#ifdef MAIN_PROGRAM X X# if !defined(LINT) && !defined(lint) X static char *copyright[] = { X "@(#) Copyright (C) 1987 by Vixie Enterprises", X "@(#) All rights reserved" X }; X# endif X X char *MONTH_NAMES[] = { X "Jan", "Feb", "Mar", "Apr", "May", "Jun", X "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", X NULL}; X char *DOW_NAMES[] = { X "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", X NULL}; X char *PROGNAME; X int LINE_NUMBER; X X# if DEBUGGING X int DEBUG_FLAGS; X char *DEBUG_FLAG_NAMES[] = { /* sync with #defines */ X "ext", "sch", "proc", "pars", "misc", X NULL}; X# endif /* DEBUGGING */ X X#else /* !MAIN_PROGRAM */ X X extern char *MONTH_NAMES[]; X extern char *DOW_NAMES[]; X extern char *PROGNAME; X extern int LINE_NUMBER; X# if DEBUGGING X extern int DEBUG_FLAGS; X extern char *DEBUG_FLAG_NAMES[]; X# endif /* DEBUGGING */ X#endif /* MAIN_PROGRAM */ X X X#endif /* _CRON_FLAG */ END_OF_cron.h if test 7420 -ne `wc -c <cron.h`; then echo shar: \"cron.h\" unpacked with wrong size! fi # end of overwriting check fi if test -f crontab.c -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"crontab.c\" else echo shar: Extracting \"crontab.c\" \(7017 characters\) sed "s/^X//" >crontab.c <<'END_OF_crontab.c' X#if !defined(lint) && !defined(LINT) Xstatic char rcsid[] = "$Header: crontab.c,v 1.5 87/05/02 17:33:22 paul Exp $"; X#endif X X/* $Source: /usr/src/local/vix/cron/crontab.c,v $ X * $Revision: 1.5 $ X * $Log: crontab.c,v $ X * Revision 1.5 87/05/02 17:33:22 paul X * baseline for mod.sources release X * X * Revision 1.4 87/03/31 13:11:48 paul X * I won't say that rs@mirror gave me this idea X * but crontab uses getopt() now X * X * Revision 1.3 87/03/30 23:43:48 paul X * another suggestion from rs@mirror: X * use getpwuid(getuid)->pw_name instead of getenv("USER") X * this is a boost to security... X * X * Revision 1.2 87/02/11 17:40:12 paul X * changed command syntax to allow append and replace instead of append as X * default and no replace at all. X * X * Revision 1.1 87/01/26 23:49:06 paul X * Initial revision X */ X X/* Copyright 1987 by Vixie Enterprises X * All rights reserved X * X * Distribute freely, except: don't sell it, don't remove my name from the X * source or documentation (don't take credit for my work), mark your changes X * (don't get me blamed for your possible bugs), don't alter or remove this X * notice. Commercial redistribution is negotiable; contact me for details. X * X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and X * I'll try to keep a version up to date. I can be reached as follows: X * Paul Vixie, Vixie Enterprises, 329 Noe Street, San Francisco, CA, 94114, X * (415) 864-7013, {ucbvax!dual,ames,ucsfmis,lll-crg,sun}!ptsfa!vixie!paul. X */ X X X#define MAIN_PROGRAM X X X#include "cron.h" X#include <pwd.h> X#include <errno.h> X#include <sys/file.h> X X Xextern char *sprintf(); X X Xstatic char USER[MAX_UNAME]; Xstatic char FILENAME[MAX_FNAME]; Xstatic int CHECK_ERROR_COUNT; Xstatic enum {opt_unknown, opt_list, opt_delete, opt_replace, opt_append} X OPTION; X X#if DEBUGGING Xstatic char *OPTIONS[] = {"???", "list", "delete", "replace", "append"}; X#endif X Xstatic void Xusage() X{ X fprintf(stderr, "usage: %s [-u user] ...\n", PROGNAME); X fprintf(stderr, " ... -l (list user's crontab)\n"); X fprintf(stderr, " ... -d (delete user's crontab)\n"); X fprintf(stderr, " ... -r file (replace user's crontab)\n"); X fprintf(stderr, " ... -a file (append file to user's crontab)\n"); X exit(ERROR_EXIT); X} X X Xvoid Xmain(argc, argv) X int argc; X char *argv[]; X{ X void parse_args(), set_cron_uid(), X append_cmd(), list_cmd(), delete_cmd(), replace_cmd(); X X PROGNAME = argv[0]; X X setlinebuf(stderr); X parse_args(argc, argv); X set_cron_uid(); X switch (OPTION) X { X case opt_list: list_cmd(); X break; X case opt_delete: delete_cmd(); X break; X case opt_replace: replace_cmd(); X append_cmd(); X break; X case opt_append: append_cmd(); X break; X } X} X X Xstatic void Xlist_cmd() X{ X extern errno; X char n[MAX_FNAME]; X FILE *f; X int ch; X X (void) sprintf(n, SPOOLDIR, USER); X if (!(f = fopen(n, "r"))) X { X if (errno == ENOENT) X fprintf(stderr, "no crontab for %s\n", USER); X else X perror(n); X exit(ERROR_EXIT); X } X X /* file it open. copy to stdout, close. X */ X Set_LineNum(1) X while (EOF != (ch = get_char(f))) X putchar(ch); X fclose(f); X} X X Xstatic void Xdelete_cmd() X{ X extern errno; X int unlink(); X void poke_daemon(); X X char n[MAX_FNAME]; X X (void) sprintf(n, SPOOLDIR, USER); X X if (unlink(n)) X { X if (errno == ENOENT) X fprintf(stderr, "no crontab for %s\n", USER); X else X perror(n); X exit(ERROR_EXIT); X } X poke_daemon(); X} X X Xstatic void Xreplace_cmd() X{ X extern errno; X int unlink(); X void poke_daemon(); X X char n[MAX_FNAME]; X X (void) sprintf(n, SPOOLDIR, USER); X X if (unlink(n)) X { X if (errno != ENOENT) X { X perror(n); X exit(ERROR_EXIT); X } X } X} X X Xstatic void Xcheck_error(msg) X char *msg; X{ X CHECK_ERROR_COUNT += 1; X fprintf(stderr, "\"%s\", line %d: %s\n", FILENAME, LINE_NUMBER, msg); X} X X Xstatic void Xappend_cmd() X{ X char *sprintf(); X entry *load_entry(); X int load_env(); X void free_entry(); X void check_error(); X void poke_daemon(); X X char n[MAX_FNAME], envstr[MAX_TEMPSTR]; X FILE *old, *new; X int ch; X entry *e; X int status; X X /* this routine is called for both 'replace' and 'append' commands X */ X X /* open the new file being installed. X */ X if (!(new = fopen(FILENAME, "r"))) X { X perror(FILENAME); X exit(ERROR_EXIT); X } X X /* check the syntax of the file being installed. X */ X X /* BUG: was reporting errors after the EOF if there were any errors X * in the file proper -- kludged it by stopping after first error. X * vix 31mar87 X */ X CHECK_ERROR_COUNT = 0; X while (!CHECK_ERROR_COUNT && ERR != (status = load_env(envstr, new))) X { X if (status == FALSE) X { X if (NULL != (e = load_entry(new, check_error))) X free((char *) e); X } X } X if (CHECK_ERROR_COUNT != 0) X { X fprintf(stderr, "errors in crontab file, can't install.\n"); X fclose(new); X exit(ERROR_EXIT); X } X rewind(new); X X /* open the old file for append-access. this depends on the file X * being created if it does not exist. X */ X (void) sprintf(n, SPOOLDIR, USER); X if (!(old = fopen(n, "a"))) X { X perror(n); X fclose(new); X exit(ERROR_EXIT); X } X X /* append the new to the old X */ X Set_LineNum(1) X while (EOF != (ch = get_char(new))) X putc(ch, old); X X /* close them and we're done X */ X fclose(old); X fclose(new); X X /* set the file to be owned by root X */ X if (ERR == chown(n, ROOT_UID, -1)) X { X perror("chown"); X exit(ERROR_EXIT); X } X X poke_daemon(); X} X X Xstatic void Xpoke_daemon() X{ X int fd; X X if (ERR == (fd = open(POKECRON, O_WRONLY|O_CREAT, 0))) X { X perror(POKECRON); X return; X } X close(fd); X} X X Xstatic void Xparse_args(argc, argv) X int argc; X char *argv[]; X{ X void usage(); X char *getenv(), *strcpy(); X int getuid(); X struct passwd *getpwnam(); X extern int getopt(), optind; X extern char *optarg; X X struct passwd *pw; X int argch; X X if (!(pw = getpwuid(getuid()))) X { X fprintf(stderr, "Your UID isn't in the passwd file.\n"); X perror("getpwuid"); X } X strcpy(USER, pw->pw_name); X FILENAME[0] = '\0'; X OPTION = opt_unknown; X while (EOF != (argch = getopt(argc, argv, "u:ldr:a:x:"))) X { X switch (argch) X { X case 'x': X if (!set_debug_flags(optarg)) X usage(); X break; X case 'u': X if (getuid() != ROOT_UID) X { X fprintf(stderr, X "must be privileged to use -u\n" X ); X exit(ERROR_EXIT); X } X if ((struct passwd *)NULL == getpwnam(optarg)) X { X fprintf(stderr, "%s: user '%s' unknown\n", X PROGNAME, optarg); X exit(ERROR_EXIT); X } X (void) strcpy(USER, optarg); X break; X case 'l': X if (OPTION != opt_unknown) X usage(); X OPTION = opt_list; X break; X case 'd': X if (OPTION != opt_unknown) X usage(); X OPTION = opt_delete; X break; X case 'a': X if (OPTION != opt_unknown) X usage(); X OPTION = opt_append; X (void) strcpy(FILENAME, optarg); X break; X case 'r': X if (OPTION != opt_unknown) X usage(); X OPTION = opt_replace; X (void) strcpy(FILENAME, optarg); X break; X default: X usage(); X } X } X X if (OPTION == opt_unknown || argv[optind] != NULL) X usage(); X X endpwent(); X X Debug(DMISC, ("user=%s, file=%s, option=%s\n", X USER, FILENAME, OPTIONS[(int)OPTION])) X} END_OF_crontab.c if test 7017 -ne `wc -c <crontab.c`; then echo shar: \"crontab.c\" unpacked with wrong size! fi # end of overwriting check fi if test -f do_command.c -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"do_command.c\" else echo shar: Extracting \"do_command.c\" \(9944 characters\) sed "s/^X//" >do_command.c <<'END_OF_do_command.c' X#if !defined(lint) && !defined(LINT) Xstatic char rcsid[] = "$Header: do_command.c,v 1.4 87/05/02 17:33:35 paul Exp $"; X#endif X X/* $Source: /usr/src/local/vix/cron/do_command.c,v $ X * $Revision: 1.4 $ X * $Log: do_command.c,v $ X * Revision 1.4 87/05/02 17:33:35 paul X * baseline for mod.sources release X * X * Revision 1.3 87/04/09 00:03:58 paul X * improved data hiding, locality of declaration/references X * fixed a rs@mirror bug by redesigning the mailto stuff completely X * X * Revision 1.2 87/03/19 12:46:24 paul X * implemented suggestions from rs@mirror (Rich $alz): X * MAILTO="" means no mail should be sent X * various fixes of bugs or lint complaints X * put a To: line in the mail message X * X * Revision 1.1 87/01/26 23:47:00 paul X * Initial revision X */ X X/* Copyright 1987 by Vixie Enterprises X * All rights reserved X * X * Distribute freely, except: don't sell it, don't remove my name from the X * source or documentation (don't take credit for my work), mark your changes X * (don't get me blamed for your possible bugs), don't alter or remove this X * notice. Commercial redistribution is negotiable; contact me for details. X * X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and X * I'll try to keep a version up to date. I can be reached as follows: X * Paul Vixie, Vixie Enterprises, 329 Noe Street, San Francisco, CA, 94114, X * (415) 864-7013, {ucbvax!dual,ames,ucsfmis,lll-crg,sun}!ptsfa!vixie!paul. X */ X X X#include "cron.h" X#include <signal.h> X#include <pwd.h> X#if defined(BSD) X# include <syslog.h> X# include <sys/wait.h> X#endif /*BSD*/ X Xvoid Xdo_command(cmd, u) X char *cmd; X user *u; X{ X extern int fork(), _exit(); X extern char *env_get(); X extern void child_process(); X X Debug(DPROC, ("do_command(%s, (%s,%d,%d))\n", X cmd, env_get(USERENV, u->envp), u->uid, u->gid)) X X /* fork to become asyncronous -- parent process is done immediately, X * and continues to run the normal cron code, which means return to X * tick(). the child and grandchild don't leave this function, alive. X * X * vfork() is unsuitable, since we have much to do, and the parent X * needs to be able to run off and fork other processes. X */ X if (fork() == 0) X { X child_process(cmd, u); X Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) X _exit(OK_EXIT); X } X Debug(DPROC, ("[%d] main process returning to work\n", getpid())) X} X X Xvoid Xchild_process(cmd, u) X char *cmd; X user *u; X{ X extern struct passwd *getpwnam(); X extern void sigpipe_func(), be_different(); X extern int VFORK(); X extern char *index(), *env_get(); X X auto int stdin_pipe[2], stdout_pipe[2]; X register char *input_data; X X X Debug(DPROC, ("[%d] child process running\n", getpid())) X X /* mark ourselves as different to PS command watchers by upshifting X * our program name. X */ X { X register char *pch; X X for (pch = PROGNAME; *pch; pch++) X *pch = MkUpper(*pch); X } X X#if defined(BSD) X /* our parent is watching for our death by catching SIGCHLD. we X * do not care to watch for our children's deaths this way -- we X * use wait() explictly. so we have to disable the signal (which X * was inherited from the parent. X * X * this isn't needed for system V, since our parent is already X * SIG_IGN on SIGCLD -- which, hopefully, will cause children to X * simply vanish when then die. X */ X (void) signal(SIGCHLD, SIG_IGN); X#endif /*BSD*/ X X /* create some pipes to talk to our future child X */ X pipe(stdin_pipe); /* child's stdin */ X pipe(stdout_pipe); /* child's stdout */ X X /* since we are a forked process, we can diddle the command string X * we were passed -- nobody else is going to use it again, right? X * X * if a % is present in the command, previous characters are the X * command, and subsequent characters are the additional input to X * the command. Subsequent %'s will be transformed into newlines, X * but that happens later. X */ X if (NULL == (input_data = index(cmd, '%'))) X { X /* no %. point input_data at a null string. X */ X input_data = ""; X } X else X { X /* % found. replace with a null (remember, we're a forked X * process and the string won't be reused), and increment X * input_data to point at the following character. X */ X *input_data++ = '\0'; X } X X /* fork again, this time so we can exec the users' command. X */ X if (VFORK() == 0) X { X Debug(DPROC, ("[%d] grandchild process VFORK()'ed\n", getpid())) X X /* get new pgrp, void tty, etc. X */ X be_different(); X X /* close the pipe ends that we won't use. this doesn't affect X * the parent, who has to read and write them; it keeps the X * kernel from recording us as a potential client TWICE -- X * which would keep it from sending SIGPIPE in otherwise X * appropriate circumstances. X */ X close(stdin_pipe[WRITE_PIPE]); X close(stdout_pipe[READ_PIPE]); X X /* grandchild process. make std{in,out} be the ends of X * pipes opened by our daddy; make stderr go to stdout. X */ X close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); X close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); X close(STDERR); dup2(STDOUT, STDERR); X X /* close the pipes we just dup'ed. The resources will remain, X * since they've been dup'ed... :-)... X */ X close(stdin_pipe[READ_PIPE]); X close(stdout_pipe[WRITE_PIPE]); X X /* set our directory, uid and gid. X */ X setgid(u->gid); /* set group first! */ X initgroups(env_get(USERENV, u->envp), u->gid); X setuid(u->uid); /* you aren't root after this... */ X chdir(env_get("HOME", u->envp)); X X /* exec the command. X */ X { X char *shell = env_get("SHELL", u->envp); X X execle(shell, shell, "-c", cmd, (char *)0, u->envp); X fprintf(stderr, "execl: couldn't exec `%s'\n", shell); X perror("execl"); X _exit(ERROR_EXIT); X } X } X X /* middle process, child of original cron, parent of process running X * the user's command. X */ X X Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) X X /* close the ends of the pipe that will only be referenced in the X * son process... X */ X close(stdin_pipe[READ_PIPE]); X close(stdout_pipe[WRITE_PIPE]); X X /* X * write, to the pipe connected to child's stdin, any input specified X * after a % in the crontab entry; we will assume that it will all X * fit, which means (on BSD anyway) that it should be 4096 bytes or X * less. this seems reasonable. while we copy, convert any X * additional %'s to newlines. when done, if some characters were X * written and the last one wasn't a newline, write a newline. X */ X X Debug(DPROC, ("[%d] child sending data to grandchild\n", getpid())) X X { X register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); X register int need_newline = FALSE; X register int escaped = FALSE; X X while (*input_data) X { X if (!escaped && *input_data == '%') X { X need_newline = FALSE; X putc('\n', out); X } X else X { X need_newline = TRUE; X putc(*input_data, out); X escaped = (*input_data == '\\'); X } X input_data++; X } X if (need_newline) X putc('\n', out); X X /* write 0-length message; this is EOF in a pipe (I hope) X */ X fflush(out); X write(stdin_pipe[WRITE_PIPE], "", 0); X fclose(out); X close(stdin_pipe[WRITE_PIPE]); X X Debug(DPROC, ("[%d] child done sending to grandchild\n", getpid())) X } X X /* X * read output from the grandchild. it's stderr has been redirected to X * it's stdout, which has been redirected to our pipe. if there is any X * output, we'll be mailing it to the user whose crontab this is... X * when the grandchild exits, we'll get EOF. X */ X X Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) X X { X register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); X register int ch; X X if (EOF != (ch = getc(in))) X { X FILE *mail; X char *usernm, *mailto; X X Debug(DPROC, ("[%d] got data (%x:%c) from grandchild\n", X getpid(), ch, ch)) X X /* get name of recipient. this is MAILTO if set to a X * valid local username; USER otherwise. X */ X usernm = env_get(USERENV, u->envp); X mailto = env_get("MAILTO", u->envp); X if (mailto) X { X /* MAILTO was present in the environment X */ X if (!*mailto) X { X /* ... but it's empty. set to NULL X */ X mailto = NULL; X } X else X { X /* not empty -- verify it, X * setting to USER if not valid. X */ X if (NULL == getpwnam(mailto)) X mailto = usernm; X endpwent(); X } X } X else X { X /* MAILTO not present, set to USER. X */ X mailto = usernm; X } X X /* if we are supposed to be mailing, MAILTO will X * be non-NULL. only in this case should we set X * up the mail command and subjects and stuff... X */ X X if (mailto) X { X extern FILE *popen(); X extern char *sprintf(); X register char **env; X auto char mailcmd[MAX_COMMAND]; X X (void) sprintf(mailcmd, MAILCMD, mailto); X if (!(mail = popen(mailcmd, "w"))) X { X perror(MAILCMD); X (void) _exit(ERROR_EXIT); X } X fprintf(mail, "To: %s\n", mailto); X fprintf(mail, "Subject: %s\n", MAILSUBJ); X fprintf(mail, "X-cron-cmd: <%s>\n", cmd); X for (env = u->envp; *env; env++) X fprintf(mail, "X-cron-env: <%s>\n", X *env); X fprintf(mail, "\n"); X X /* this was the first char from the pipe X */ X putc(ch, mail); X } X X /* we have to read the input pipe no matter whether X * we mail or not, but obviously we only write to X * mail pipe if we ARE mailing. X */ X X while (EOF != (ch = getc(in))) X { X if (mailto) X putc(ch, mail); X } X X /* only close pipe if we opened it -- i.e., we're X * mailing... X */ X X if (mailto) X pclose(mail); X X } /*if data from grandchild*/ X X Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) X X fclose(in); X close(stdout_pipe[READ_PIPE]); X } X X#if defined(BSD) X /* wait for child to die. X */ X { X int pid; X union wait waiter; X X Debug(DPROC, ("[%d] waiting for grandchild to finish\n", getpid())) X pid = wait(&waiter); X Debug(DPROC, ("[%d] grandchild #%d finished, status=%d\n", X getpid(), pid, waiter.w_status)) X } X#endif /*BSD*/ X} END_OF_do_command.c if test 9944 -ne `wc -c <do_command.c`; then echo shar: \"do_command.c\" unpacked with wrong size! fi # end of overwriting check fi if test -f entry.c -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"entry.c\" else echo shar: Extracting \"entry.c\" \(9326 characters\) sed "s/^X//" >entry.c <<'END_OF_entry.c' X#if !defined(lint) && !defined(LINT) Xstatic char rcsid[] = "$Header: entry.c,v 1.2 87/05/02 17:33:51 paul Exp $"; X#endif X X/* $Source: /usr/src/local/vix/cron/entry.c,v $ X * $Revision: 1.2 $ X * $Log: entry.c,v $ X * Revision 1.2 87/05/02 17:33:51 paul X * baseline for mod.sources release X * X * Revision 1.1 87/01/26 23:47:32 paul X * Initial revision X * X * vix 01jan87 [added line-level error recovery] X * vix 31dec86 [added /step to the from-to range, per bob@acornrc] X * vix 30dec86 [written] X */ X X X/* Copyright 1987 by Vixie Enterprises X * All rights reserved X * X * Distribute freely, except: don't sell it, don't remove my name from the X * source or documentation (don't take credit for my work), mark your changes X * (don't get me blamed for your possible bugs), don't alter or remove this X * notice. Commercial redistribution is negotiable; contact me for details. X * X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and X * I'll try to keep a version up to date. I can be reached as follows: X * Paul Vixie, Vixie Enterprises, 329 Noe Street, San Francisco, CA, 94114, X * (415) 864-7013, {ucbvax!dual,ames,ucsfmis,lll-crg,sun}!ptsfa!vixie!paul. X */ X X X#include "cron.h" X Xtypedef enum X {e_none, e_minute, e_hour, e_dom, e_month, e_dow, e_cmd} X ecode_e; Xstatic char *ecodes[] = X { X "no error", X "bad minute", X "bad hour", X "bad day-of-month", X "bad month", X "bad day-of-week", X "bad command" X }; X Xvoid Xfree_entry(e) X entry *e; X{ X int free(); X X (void) free((char *) e); X} X X Xentry * Xload_entry(file, error_func) X FILE *file; X void (*error_func)(); X{ X /* this function reads one crontab entry -- the next -- from a file. X * it skips any leading blank lines, ignores comments, and returns X * EOF if for any reason the entry can't be read and parsed. X * X * the entry IS parsed here, btw. X * X * syntax: X * minutes hours doms months dows cmd\n X */ X X int free(); X char *malloc(), *savestr(), get_list(); X X ecode_e ecode = e_none; X entry *e; X int ch; X void skip_comments(); X char *pc; X char cmd[MAX_COMMAND]; X X e = (entry *) malloc(sizeof(entry)); X X Debug(DPARS, ("load_entry()...about to eat comments\n")) X X skip_comments(file); X X ch = get_char(file); X X /* ch is now the first useful character of a useful line, which means: X * the first character of a list of minutes. X */ X X Debug(DPARS, ("load_entry()...about to parse numerics\n")) X X ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, PPC_NULL, ch, file); X if (ch == EOF) X { X ecode = e_minute; X goto eof; X } X X /* hours X */ X X ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, PPC_NULL, ch, file); X if (ch == EOF) X { X ecode = e_hour; X goto eof; X } X X /* DOM (days of month) X */ X X e->dom_star = (ch == '*'); X Debug(DPARS, ("ch=%c, dom_star=%d\n", ch, e->dom_star)) X ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file); X if (ch == EOF) X { X ecode = e_dom; X goto eof; X } X X /* month X */ X X ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, MONTH_NAMES, ch, file); X if (ch == EOF) X { X ecode = e_month; X goto eof; X } X X /* DOW (days of week) X */ X X e->dow_star = (ch == '*'); X Debug(DPARS, ("ch=%c, dow_star=%d\n", ch, e->dow_star)) X ch = get_list(e->dow, FIRST_DOW, LAST_DOW, DOW_NAMES, ch, file); X if (ch == EOF) X { X ecode = e_dow; X goto eof; X } X X /* make sundays equivilent */ X if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) X { X bit_set(e->dow, 0) X bit_set(e->dow, 7) X } X X Debug(DPARS, ("load_entry()...about to parse command\n")) X X /* ch is first character of a command. everything up to the next X * \n or EOF is part of the command... too bad we don't know in X * advance how long it will be, since we need to malloc a string X * for it... so, we limit it to MAX_COMMAND X */ X pc = cmd; X while (ch != '\n' && ch != EOF) X { X *pc++ = ch; X ch = get_char(file); X } X /* a file without a \n before the EOF is rude, so we'll complain... X */ X if (ch == EOF) X { X ecode = e_cmd; X goto eof; X } X X /* got the command in the 'cmd' string; add a null and save it in *e. X */ X *pc = '\0'; X e->cmd = savestr(cmd); X X Debug(DPARS, ("load_entry()...returning successfully\n")) X X /* success, fini, return pointer to the entry we just created... X */ X return e; X Xeof: /* if we want to return EOF, we have to jump down here and X * free the entry we've been building. X * X * now, in some cases, a parse routine will have returned EOF to X * indicate an error, but the file is not actually done. since, in X * that case, we only want to skip the line with the error on it, X * we'll do that here. X * X * many, including the author, see what's below as evil programming X * practice: since I didn't want to change the structure of this X * whole function to support this error recovery, I recurse. Cursed! X * I'm seriously considering using a GOTO... argh! X */ X X (void) free((char *) e); X X if (feof(file)) X return NULL; X X if (error_func) X (*error_func)(ecodes[(int)ecode]); X do {ch = get_char(file);} X while (ch != EOF && ch != '\n'); X if (ch == EOF) X return NULL; X return load_entry(file, error_func); X} X X Xstatic char Xget_list(bits, low, high, names, ch, file) X bit_ref( bits ) /* one bit per flag, default=FALSE */ X int low, high; /* bounds, impl. offset for bitstr */ X char *names[]; /* NULL or *[] of names for these elements */ X int ch; /* current character being processed */ X FILE *file; /* file being read */ X{ X char get_range(); X int done; X X /* we know that we point to a non-blank character here; X * must do a Skip_Blanks before we exit, so that the X * next call (or the code that picks up the cmd) can X * assume the same thing. X */ X X Debug(DPARS|DEXT, ("get_list()...entered\n")) X X /* list = "*" | range {"," range} X */ X X if (ch == '*') X { X /* '*' means 'all elements'. X */ X bit_setall(bits, (high-low+1)) X goto exit; X } X X /* clear the bit string, since the default is 'off'. X */ X bit_clearall(bits, (high-low+1)) X X /* process all ranges X */ X done = FALSE; X while (!done) X { X ch = get_range(bits, low, high, names, ch, file); X if (ch == ',') X ch = get_char(file); X else X done = TRUE; X } X Xexit: /* exiting. skip to some blanks, then skip over the blanks. X */ X Skip_Nonblanks(ch, file) X Skip_Blanks(ch, file) X X Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch)) X X return ch; X} X X Xstatic char Xget_range(bits, low, high, names, ch, file) X bit_ref( bits ) /* one bit per flag, default=FALSE */ X int low, high; /* bounds, impl. offset for bitstr */ X char *names[]; /* NULL or names of elements */ X int ch; /* current character being processed */ X FILE *file; /* file being read */ X{ X /* range = number | number "-" number [ "/" number ] X */ X X char get_number(); X int i, num1, num2, num3; X X Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")) X X if (EOF == (ch = get_number(&num1, low, names, ch, file))) X return EOF; X X if (ch != '-') X { X /* not a range, it's a single number. X */ X if (EOF == set_element(bits, low, high, num1)) X return EOF; X } X else X { X /* eat the dash X */ X ch = get_char(file); X if (ch == EOF) X return EOF; X X /* get the number following the dash X */ X ch = get_number(&num2, low, names, ch, file); X if (ch == EOF) X return EOF; X X /* check for step size X */ X if (ch == '/') X { X /* eat the slash X */ X ch = get_char(file); X if (ch == EOF) X return EOF; X X /* get the step size -- note: we don't pass the X * names here, because the number is not an X * element id, it's a step size. 'low' is X * sent as a 0 since there is no offset either. X */ X ch = get_number(&num3, 0, PPC_NULL, ch, file); X if (ch == EOF) X return EOF; X } X else X { X /* no step. default==1. X */ X num3 = 1; X } X X /* range. set all elements from num1 to num2, stepping X * by num3. (the step is a downward-compatible extension X * proposed conceptually by bob@acornrc, syntactically X * designed then implmented by paul vixie). X */ X for (i = num1; i <= num2; i += num3) X if (EOF == set_element(bits, low, high, i)) X return EOF; X } X return ch; X} X X Xstatic char Xget_number(numptr, low, names, ch, file) X int *numptr; X int low; X char *names[]; X char ch; X FILE *file; X{ X char temp[MAX_TEMPSTR], *pc; X int len, i, all_digits; X X /* collect alphanumerics into our fixed-size temp array X */ X pc = temp; X len = 0; X all_digits = TRUE; X while (isalnum(ch)) X { X if (++len >= MAX_TEMPSTR) X return EOF; X X *pc++ = ch; X X if (!isdigit(ch)) X all_digits = FALSE; X X ch = get_char(file); X } X *pc = '\0'; X X /* try to find the name in the name list X */ X if (names) X for (i = 0; names[i] != NULL; i++) X { X Debug(DPARS|DEXT, X ("get_num, compare(%s,%s)\n", names[i], temp)) X if (!nocase_strcmp(names[i], temp)) X { X *numptr = i+low; X return ch; X } X } X X /* no name list specified, or there is one and our string isn't X * in it. either way: if it's all digits, use its magnitude. X * otherwise, it's an error. X */ X if (all_digits) X { X *numptr = atoi(temp); X return ch; X } X X return EOF; X} X X Xstatic int Xset_element(bits, low, high, number) X bit_ref( bits ) /* one bit per flag, default=FALSE */ X int low; X int high; X int number; X{ X Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)) X X if (number < low || number > high) X return EOF; X X Debug(DPARS|DEXT, ("bit_set(%x,%d)\n",bits,(number-low))) X bit_set(bits, (number-low)) X Debug(DPARS|DEXT, ("bit_set succeeded\n")) X return OK; X} END_OF_entry.c if test 9326 -ne `wc -c <entry.c`; then echo shar: \"entry.c\" unpacked with wrong size! fi # end of overwriting check fi echo shar: End of archive 2 \(of 2\). cp /dev/null ark2isdone MISSING="" for I in 1 2 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked both archives. rm -f ark[1-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0