bcron-0.09/0000775000076400007640000000000010251704762012116 5ustar bruceguenterbcron-0.09/NEWS0000664000076400007640000000442710251704762012624 0ustar bruceguenter------------------------------------------------------------------------------- Changes in version 0.09 - Fixed several build and portability issues. - The default path for bcron-spool is changed to /var/run/bcron-spool Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 0.08 - Reordered some things in bcron-exec to provide error emails more often when jobs fail to run because of system errors. - bcron-update internally strips slashes from paths to prevent problems with doubled or trailing slashes in arguments. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 0.07 - Added logging of warnings when crontabs fail to be parsed properly. - Added sending email notification if a job was killed. - Fixed crash in parsing of faulty timespecs. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 0.06 - Added support for listing all system crontabs. - bcron-exec now waits until all the programs its running have exited so that email gets sent properly. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 0.05 - Die with error if executing the shell or wrapper failed in bcron-exec. - Set up supplemental groups in bcron-exec. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 0.04 - Fixed (for good this time?) the bug in bcron-exec causing delayed execution of programs. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- bcron-0.09/TODO0000664000076400007640000000175510251704762012616 0ustar bruceguenter-FIXME: Disallow setting $LOGNAME and $USER in crontabs. -Put info file into appropriate place in spec. -Add support for binary (pre-parsed) crontabs. -Send email if cron job parsing fails. -Allow for some special address for system crontabs. crontab.c:crontab_load: -merge jobs together, not just drop them timespec-next.c:timespec_next: -Fix documentation on scheduling limits (10 year limit) bcron-exec: -Drop the use of command IDs -Don't report on execution success -(minor)Figure out how to open the temporary file with O_APPEND bcron-start: bcron-sched: -If time has jumped backwards or forwards by too large an interval (1 hour?), recalculate all times. -Fix spurious double-reloading of cronjobs updated by bcron-update. bcron-update: -Update just before the top of the minute, instead of just every minute? bcron-spool: -Implement support for $BCRON_MAXSIZE and $BCRON_MAXJOBS bcrontab: -Make the temporary directory configurable via $TMPDIR or something. tests: -Write more tests! bcron-0.09/bcron-0.09.spec0000664000076400007640000000462610251704762014471 0ustar bruceguenterName: bcron Summary: Bruce's Cron System Version: 0.09 Release: 1 Copyright: GPL Group: Utilities/System Source: http://untroubled.org/bcron/bcron-0.09.tar.gz BuildRoot: %{_tmppath}/bcron-buildroot URL: http://untroubled.org/bcron/ Packager: Bruce Guenter BuildRequires: bglibs >= 1.021 Requires: ucspi-unix Requires: supervise-scripts >= 3.5 Conflicts: vixie-cron Conflicts: fcron Conflicts: dcron %description Bruce's Cron System %prep %setup echo gcc "%{optflags}" >conf-cc echo gcc -s >conf-ld echo %{_bindir} >conf-bin %build make %install rm -fr %{buildroot} mkdir -p %{buildroot}%{_bindir} make install_prefix=%{buildroot} install mkdir -p %{buildroot}%{_mandir}/man{1,8} cp bcron-{exec,sched,spool,start,update}.8 %{buildroot}%{_mandir}/man8 cp bcrontab.1 %{buildroot}%{_mandir}/man1 mkdir -p %{buildroot}/var/service/bcron-{sched/log,spool,update} install -m 755 bcron-sched.run %{buildroot}/var/service/bcron-sched/run install -m 755 bcron-sched-log.run %{buildroot}/var/service/bcron-sched/log/run install -m 755 bcron-spool.run %{buildroot}/var/service/bcron-spool/run install -m 755 bcron-update.run %{buildroot}/var/service/bcron-update/run chmod +t %{buildroot}/var/service/bcron-sched mkdir -p %{buildroot}/var/log/bcron mkdir -p %{buildroot}/var/spool/cron/{crontabs,tmp} mkfifo %{buildroot}/var/spool/cron/trigger mkdir -p %{buildroot}/etc/bcron mkdir -p %{buildroot}/etc/cron.d %clean rm -rf %{buildroot} %pre grep -q '^cron:' /etc/group \ || groupadd -r cron grep -q '^cron:' /etc/passwd \ || useradd -r -d /var/spool/cron -s /sbin/nologin -g cron cron %post PATH="$PATH:/usr/local/bin" if [ "$1" = 1 ]; then for svc in bcron-sched bcron-spool bcron-update; do if ! [ -e /service/$svc ]; then svc-add $svc fi done else for svc in bcron-sched bcron-spool bcron-update; do svc -t /service/$svc done fi %preun if [ "$1" = 0 ]; then for svc in bcron-sched bcron-spool bcron-update; do if [ -L /service/$svc ]; then svc-remove $svc fi done fi %files %defattr(-,root,root) %doc ANNOUNCEMENT COPYING NEWS README %doc bcron.texi bcron.html %config %dir /etc/bcron %config %dir /etc/cron.d %{_bindir}/* %{_mandir}/*/* /var/service/* %attr(700,cron,cron) %dir /var/spool/cron %attr(700,cron,cron) %dir /var/spool/cron/crontabs %attr(700,cron,cron) %dir /var/spool/cron/tmp %attr(600,cron,cron) /var/spool/cron/trigger %attr(700,root,root) %dir /var/log/bcron bcron-0.09/conf-cc0000664000076400007640000000013210251704762013345 0ustar bruceguentergcc -W -Wall -Wshadow -O3 -g -I/usr/local/include This will be used to compile .c files. bcron-0.09/conf-ld0000664000076400007640000000012710251704762013363 0ustar bruceguentergcc -g -L/usr/local/lib This will be used to link .o and .a files into an executable. bcron-0.09/Makefile0000664000076400007640000000735610251704762013571 0ustar bruceguenter# Don't edit Makefile! Use conf-* for configuration. # # Generated by spac see http://untroubled.org/spac/ SHELL=/bin/sh DEFAULT: all all: libraries programs docs bcron-exec: load bcron-exec.o bcron.a ./load bcron-exec bcron.a -lbg bcron-exec.o: compile bcron-exec.c bcron.h ./compile bcron-exec.c bcron-sched: load bcron-sched.o bcron.a ./load bcron-sched bcron.a -lbg bcron-sched.o: compile bcron-sched.c bcron.h ./compile bcron-sched.c bcron-spool: load bcron-spool.o bcron.a ./load bcron-spool bcron.a -lbg bcron-spool.o: compile bcron-spool.c bcron.h ./compile bcron-spool.c bcron-start: load bcron-start.o bcron.a ./load bcron-start bcron.a -lbg bcron-start.o: compile bcron-start.c bcron.h ./compile bcron-start.c bcron-update: load bcron-update.o bcron.a ./load bcron-update bcron.a -lbg bcron-update.o: compile bcron-update.c bcron.h ./compile bcron-update.c bcron.a: makelib chdir.o connection.o crontab.o crontabs.o job.o ministat.o sendpacket.o tempfile.o time.o timespec-next.o timespec-parse.o ./makelib bcron.a chdir.o connection.o crontab.o crontabs.o job.o ministat.o sendpacket.o tempfile.o time.o timespec-next.o timespec-parse.o bcron.html: bcron.texi texi2html --toc-file=bcron.html --top-file=bcron.html bcron.texi bcron.info: bcron.texi makeinfo bcron.texi bcrontab: load bcrontab.o bcron.a ./load bcrontab bcron.a -lbg-cli -lbg bcrontab.o: compile bcrontab.c bcron.h ./compile bcrontab.c chdir.o: compile chdir.c bcron.h ./compile chdir.c clean: TARGETS rm -f `cat TARGETS` clean-spac: clean AUTOFILES rm -f `cat AUTOFILES` compile: conf-cc conf-bgincs ( bgincs=`head -n 1 conf-bgincs`; \ echo '#!/bin/sh'; \ echo 'source=$$1; shift'; \ echo 'base=`echo "$$source" | sed -e s:\\\\.c$$::`'; \ echo exec `head -n 1 conf-cc` -I. "-I'$${bgincs}'" '-o $${base}.o -c $$source $${1+"$$@"}'; \ ) >compile chmod 755 compile conf_bin.c: conf-bin head -n 1 conf-bin | \ sed -e 's/"/\\"/g' \ -e 's/^/const char conf_bin[] = "/' \ -e 's/$$/";/' >conf_bin.c connection.o: compile connection.c bcron.h ./compile connection.c crontab.o: compile crontab.c bcron.h ./compile crontab.c crontabs.o: compile crontabs.c bcron.h ./compile crontabs.c docs: bcron.info bcron.html install: installer instcheck ./installer ./instcheck installer: load insthier.o ./load insthier -lbg-installer -lbg mv insthier installer instcheck: load insthier.o ./load insthier -lbg-instcheck -lbg mv insthier instcheck insthier.o: compile insthier.c conf_bin.c ./compile insthier.c instshow: load insthier.o ./load insthier -lbg-instshow -lbg mv insthier instshow job.o: compile job.c bcron.h ./compile job.c libraries: bcron.a load: conf-ld conf-bglibs ( bglibs=`head -n 1 conf-bglibs`; \ echo '#!/bin/sh';\ echo 'main="$$1"; shift';\ echo exec `head -n 1 conf-ld` -L. "-L'$${bglibs}'" '-o "$$main" "$$main.o" $${1+"$$@"}' -lbg-sysdeps; \ ) >load chmod 755 load makelib: ( echo '#!/bin/sh'; \ echo 'main="$$1"; shift';\ echo 'rm -f "$$main"';\ echo 'ar cr "$$main" $${1+"$$@"}';\ echo 'ranlib "$$main"';\ ) >makelib chmod 755 makelib ministat.o: compile ministat.c bcron.h ./compile ministat.c programs: installer instcheck instshow bcron-sched bcrontab bcron-spool bcron-start bcron-update bcron-exec test-sched sendpacket.o: compile sendpacket.c bcron.h ./compile sendpacket.c tempfile.o: compile tempfile.c bcron.h ./compile tempfile.c test-sched: load test-sched.o bcron.a ./load test-sched bcron.a -lbg-cli -lbg test-sched.o: compile test-sched.c bcron.h ./compile test-sched.c time.o: compile time.c bcron.h ./compile time.c timespec-next.o: compile timespec-next.c bcron.h ./compile timespec-next.c timespec-parse.o: compile timespec-parse.c bcron.h ./compile timespec-parse.c bcron-0.09/ministat.c0000664000076400007640000000172710251704762014121 0ustar bruceguenter#include #include #include #include #include #include #include #include "bcron.h" static void copystat(struct ministat* m, const struct stat* s) { memset(m, 0, sizeof *m); m->exists = 1; m->device = s->st_dev; m->inode = s->st_ino; m->mode = s->st_mode; m->size = s->st_size; m->mtime = s->st_mtime; } void minifstat(int fd, struct ministat* s) { struct stat st; if (fstat(fd, &st) != 0) die1sys(111, "Could not fstat"); else copystat(s, &st); } void ministat(const char* path, struct ministat* s) { struct stat st; if (stat(path, &st) != 0) { if (errno == ENOENT) memset(s, 0, sizeof *s); else die3sys(111, "Could not stat '", path, "'"); } else copystat(s, &st); } void ministat2(const char* base, const char* entry, struct ministat* s) { static str path; wrap_str(str_copy3s(&path, base, "/", entry)); ministat(path.s, s); } bcron-0.09/FILES0000664000076400007640000000113110251704762012677 0ustar bruceguenterANNOUNCEMENT AUTOFILES COPYING ChangeLog FILES Makefile NEWS README SRCFILES TARGETS TODO VERSION bcron-0.09.spec bcron-exec.8 bcron-exec.c bcron-sched-log.run bcron-sched.8 bcron-sched.c bcron-sched.run bcron-spool.8 bcron-spool.c bcron-spool.run bcron-start.8 bcron-start.c bcron-update.8 bcron-update.c bcron-update.run bcron.h bcron.html bcron.info bcron.texi bcrontab.1 bcrontab.c chdir.c conf-bgincs conf-bglibs conf-bin conf-cc conf-ld connection.c crontab.5 crontab.c crontabs.c insthier.c job.c ministat.c sendpacket.c tempfile.c test-sched.c tests.sh time.c timespec-next.c timespec-parse.c bcron-0.09/crontabs.c0000664000076400007640000000342410251704762014100 0ustar bruceguenter#include #include #include #include #include #include "bcron.h" static void crontabp_free(struct crontab* c) { job_free(c->jobs); } GHASH_DEFN(crontabs,const char*,struct crontab, adt_hashsp,adt_cmpsp,adt_copysp,0,adt_freesp,crontabp_free); struct ghash crontabs; static str path; static void unload(const char* name) { msg3("Unloading '", name, "'"); crontabs_remove(&crontabs, &name); } static void reload(struct crontabs_entry* entry) { const char* name = entry->key; msg3("Reloading '", name, "'"); wrap_str(str_copy2s(&path, CRONTAB_DIR "/", name)); if (!crontab_import(&entry->data, path.s, name[0] == ':' ? 0 : name)) warn3("Reloading '", name, "' failed, using old table"); } static void load(const char* name) { struct crontab c; msg3("Loading '", name, "'"); wrap_str(str_copy2s(&path, CRONTAB_DIR "/", name)); memset(&c, 0, sizeof c); if (crontab_import(&c, path.s, name[0] == ':' ? 0 : name)) { if (!crontabs_add(&crontabs, &name, &c)) die_oom(111); } else warn3("Loading '", name, "' failed"); } void crontabs_load(void) { DIR* dir; const direntry* de; struct crontabs_entry* entry; struct ghashiter iter; const char* name; struct ministat st; if ((dir = opendir(CRONTAB_DIR)) == 0) die1sys(111, "Could not open crontabs directory"); while ((de = readdir(dir)) != 0) { name = de->d_name; if (name[0] != '.') { if ((entry = crontabs_get(&crontabs, &name)) == 0) load(name); } } closedir(dir); ghashiter_loop(&iter, &crontabs) { entry = iter.entry; ministat2(CRONTAB_DIR, entry->key, &st); if (!st.exists) unload(entry->key); else if (memcmp(&st, &entry->data.stat, sizeof st) != 0) reload(entry); } } bcron-0.09/job.c0000664000076400007640000000262410251704762013040 0ustar bruceguenter#include #include #include #include #include #include #include #include #include #include "bcron.h" void job_free(struct job* job) { if (job) { job_free(job->next); if (job->runas != 0) free((char*)job->runas); if (job->command != 0) free((char*)job->command); str_free(&job->environ); free(job); } } struct job* job_new(const struct job_timespec* times, const char* runas, const char* command, const str* environ) { struct job* this; if ((this = malloc(sizeof *this)) == 0) die_oom(111); memset(this, 0, sizeof *this); memcpy(&this->times, times, sizeof *times); if ((runas && (this->runas = strdup(runas)) == 0) || (this->command = strdup(command)) == 0 || !str_copy(&this->environ, environ)) die_oom(111); return this; } void job_exec(struct job* job) { static str packet; static unsigned long id = 1; debug2(DEBUG_JOBS, "Running: ", job->command); packet.len = 0; str_catu(&packet, id++); str_catc(&packet, 0); if (job->runas != 0) str_catb(&packet, job->runas, strlen(job->runas) + 1); else str_catc(&packet, 0); str_catb(&packet, job->command, strlen(job->command) + 1); str_cat(&packet, &job->environ); if (sendpacket(1, &packet) == -1) die1sys(111, "Could not send job to bcron-exec"); } bcron-0.09/bcron.html0000664000076400007640000015433110251704762014116 0ustar bruceguenter bcron documentation: Top
[Top] [Contents] [Index] [ ? ]

bcron documentation


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1. Introduction

Name comes from: Bruce's / Better / Busy cron.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.1 Problems With Other cron Systems


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.1.1 vixie-cron

  • Too many bugs The last official release of vixie-cron was in December of 1993. Since then, many security holes and other bugs have been found, requiring every vendor to distribute a multiply-patched version of this system. Most vendors have a different set of patches, making this a bit of a moving target.
  • Can't handle DST changes vixie-cron's mode of operations is to wake up every minute and determine, with no history, what jobs to run. When DST changes, this causes some jobs to be either skipped or run twice.

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.1.2 fcron

  • Parsing issues If a job starts with a word (terminated by any non-word character, like /) that happens to also be a username, fcron interprets this word as the userid to run the job under, even for non-root users. This causes problems for jobs like bin/something when user bin exists.
  • No support for `/etc/cron.d'
  • No support for `/etc/crontab'
  • Gratuitious incompatibilities with vixie-cron Jobs are run with the login shell for the user instead of `/bin/sh'. The default mode of execution prevents the same job from running multiple times (a good option, but bad default). MAILTO can only contain a username, not a full email address.

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.1.3 anacron

Anacron is only really useful for running jobs once a day or less frequently. From what I've seen, it's good at what it does, just not useful at much else.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.1.4 dcron

  • No support for `/etc/cron.d'
  • No support for `/etc/crontab'
  • No easy way of emulating support for system crontabs dcron's crontab format does not include a "username" column, and as such makes it nearly impossible to even emulate system crontabs.
  • Unusual or broken handling of DST changes From reading the source, dcron only handles cases where the linear time jumps. When DST changes, linear time does not change, and so dcron effectively does not handle this situation.

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.2 Requirements

  • Security

    I am choosing to make a number of relatively unorthodox choices in order to avoid many of the security issues that have plagued vixie-cron and other related systems.

  • External compatability with vixie-cron

    In particular, the system must support:

    • crontabs in the same file format as vixie-cron
    • crontabs submitted via a command-line compatable `crontab' program
    • mail is sent to $MAILTO if the job produces any output (and possibly if the job exits non-zero)
  • Support for system crontabs

    This means that the system MUST support:

    • System crontab entries in `/etc/crontab'
    • System crontab entries in `/etc/cron.d/*'

    System crontab entries are additionally differentiated from normal ones by having a "username" column immediately preceding the command.

  • Intelligent handling of DST time changes

    One of the biggest frustrations I have had with dealing with vixie-cron is its complete inability to deal with time jumps in an intelligent manner. In particular, when DST changes happen, jobs will either get skipped (when time jumps forward) or executed twice (when time jumps backwards). This is unacceptable.

  • Allow setting ulimits before executing commands

    In one of the target installations, we need to set up ulimits (limiting CPU time and memory) before executing commands. It would be easy enough to ulimit the entire daemon, but then the daemon itself would be vulnerable to getting killed when it has run for long enough. Our current setup is to run jobs through a global wrapper script, which can set any necessary limits (or anything else) and then execute the job.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.3 Design Choices

Use local socket to submit files.

There are two basic methods of submitting crontab files:

  1. Use a setuid program to write directly into spool files.
  2. Set up a local socket to submit jobs to a daemon.

(1) Using a setuid submission agent was discarded to prevent the possibility of all the bugs that have plagued other setuid submission agents. The socket protocol is deliberately very simple, to make the submission agent foolproof.

Multiple process daemon.

By seperating job submission from job execution, exploiting the system to run arbitrary jobs as privileged users is made even harder. It also makes the design of those individual programs much simpler.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.4 vixie-cron Patches

This section lists all the non-trivial patches found for vixie-cron, what problem they appear to address, and (if appropriate) how bcron will avoid the same problem. The patches listed come from multiple sources, including the latest RPM (Fedora Core IIRC).

0days.patch

This patch modifies the `crontab.5' man page to remove allowing `0' for day of month or month numbers.

badsig.patch

On some systems, signal handlers are single-shot. This patch modifies the SIGHUP handler to reactivate itself before it returns. bcron will use the bglibs signal functions, which use sigaction to create suitable handlers, where appropriate. bcron doesn't use signals for any purpose.

buffer.patch

This patch increases the maximum username length from 20 to 32, and modifies calls to strcpy to use strncpy to ensure all string copies are length bounded. bcron uses dynamically allocated strings to eliminate the possibility of buffer overflows.

close_stdin.diff

This patch modifies the cron daemon to close stdin, stdout, and stderr on startup, and to reopen them as `/dev/null'. The bcron daemons run under supervise, and have no need of such handling.

crond.patch

Adds support for `/etc/cron.d'

cront_stdin.patch

Appears to modify `crontab''s command-line handling such that no argument is interpreted as to read the crontab from standard input.

crontab.5.diff

Documents several builtin macros to replace the first 5 fields. This macros consist of: `@reboot', `@yearly', `@annually', `@monthly', `@weekly', `@daily', `@midnight', and `@hourly'. bcron will not, at least initially, support these macros.

crontab.patch

Modifies crontab to use strncpy and snprintf when writing into length-bounded strings.

crontabloc.patch

Patches the crontab man page to reference `/etc/crontab'.

dst.patch

Patches the crontab man page to point out that DST may cause jobs to be skipped or repeated.

name.patch

Appears to modify how the cron daemon handles sending messages to syslog. bcron will log messages to stderr, avoiding syslog entirely.

nodot.patch

Adds `-i' to the list of arguments sent to sendmail (result is `-FCronDaemon -i -odi -oem'). Only useful for sendmail, but still needed.

root_-u-85879.patch

Sanity checks the use of `-u' against UID and/or root.

security2.patch

Does some sanity checking on mailto, and does a setuid before sending mail. bcron plays safe with mailto by putting it into a message header, and always drops root privileges before executing commands.

sigchld.patch

Return the SIGCHLD handler to its default state before executing commands.

sprintf.patch

More sprintf -> snprintf conversions.

time.patch

Sync all the crontabs before sleeping to handle changes in the system time.

timeaftertime.patch

The previous patch created double execution issues with small backwards adjustments in the clock time.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2. Design Notes


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.1 Fundamental Operations

The following is a list of all the "core" operations that must be provided based on the requirements.

Execute jobs

Strictly speaking, this is the only role that requires superuser privileges. Every other job should run as non-root.

Schedule jobs

Scan all the known jobs, determine which one needs to be executed next, and sleep until that time arrives.

Accept new user crontabs

Listen for connections on a socket and accept job data.

Parse crontabs into internal job format

Read the submitted files and parse their contents into a structured format.

Check for new system crontabs

Check `/etc/crontab' and `/etc/cron.d/*' every minute for modifications. If any files are changed, added, or deleted, add the listed jobs. On systems with tightened security, these files may only be readable by `root'.

Manage saved state

All jobs need to be saved to disk along with when they were last executed, in order to determine when they should be next executed.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2 Programs

`bcron-sched'

Top-level scheduler agent. Starts up as root, runs `bcron-exec', and then drops root permanently.

`bcron-exec'

Accepts jobs to run from `bcron-sched' on stdin, writes exit status back to stdout.

`bcron-spool'

Manages the cron spool: receives jobs submitted from users, writes them to files in `/var/spool/cron/crontabs', and notifies `bcron-sched'. This needs to be run from `unixserver' in order to securely determine the invoking UID. This program will optionally run the file through an external filter, specified on the command line, before installing the job.

`bcron-update'

Watches for changes to the system crontabs and notifies `bcron-sched'.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3 Files


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3.1 File Hierarchy

`/etc/cron.d/'
`/etc/cron.d/*'
`/etc/crontab'

The above three items are read

`/var/spool/cron/'
`/var/spool/cron/crontabs/'

Directory containing raw (text) crontab files.

`/var/spool/cron/crontabs/:etc:cron.d:*'

Colon is chosen as a seperator because usernames cannot contain colons due to the format of `/etc/passwd'.

`/var/spool/cron/crontabs/:etc:crontab'
`/var/spool/cron/bcrontabs/'

Directory containing pre-parsed (aka compiled) crontab files (Not yet implemented).

`/var/spool/cron/tmp/'

Temporary directory for files as they are written.

`/var/spool/cron/trigger'

Named pipe used to tell `bcron-sched' to rescan the crontabs.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4 Inter-Process Communication

All communication between programs is done in terms of either "packets" or "lines". A packet is formatted as a netstring. That is, a packet of length N is encoded as the ASCII decimal value of N, `:', N bytes of data, terminated by `,'. A line is simply a series of non-NUL bytes terminated by a NUL byte.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4.1 Job Submission Protocol

Client sends a packet containing a single byte command followed by the username. If the command requires additional data, it is seperated from the username by a NUL byte. Server responds with a packet containing a response byte followed by a text message.

Client command codes are:

S

Submit a user crontab file. The content string contains the entire crontab file.

L

List the current crontab file. No content string.

R

Remove any previously submitted crontab file. No content string.

Y

List all system crontabs. No content string. This command is only available to users `root' and `cron'.

Server response codes are:

K

Command was successful; file was parsed and accepted.

D

File could not be parsed.

Z

Temporary internal error.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4.2 bcron-exec Protocol

Input packets contain a series of four or more NUL-terminated lines:

ID
username
command
environment

The environment is optional. If the environment contains SHELL, it replaces the default shell (`/bin/sh'). If the environment contains MAILTO, it overrides the default mailing address derived from the username.

Output packet:

ID
NUL
response code
text message

Output packets are sent asynchronously with respect to input packets.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3. Configuration


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.1 Environment Variables

BCRON_SPOOL

The base directory for bcron's files. Defaults to `/var/spool/cron'.

BCRON_USER

The non-root user name to switch to for all processes that don't require root privileges. Defaults to `cron'.

BCRON_MAXSIZE

The maximum size (in bytes) of a single user crontab. Defaults to unlimited.

BCRON_MAXJOBS

The maximum number of jobs in a single user crontab. Defaults to unlimited.

BCRON_SOCKET

The full path to the UNIX-domain socket used to submit crontabs. Defaults to `/var/run/bcron-spool'.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4. Implementation Notes


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1 Job Scheduler

Getting the job scheduler to work correctly for all possible cases consumed more time than all the other parts of the program put together, and this is all because of the problems that daylight savings causes.

The ultimate goal is this: given a linear timestamp, determine the next linear timestamp after which a job must be run. "Linear time" means the number of seconds since an epoch, in this case the UNIX epoch, midnight GMT January 1st, 1970. To contrast, "local time" means the time in terms of years, months, days, hours, minutes, and seconds in the current locale.

The time specification for jobs is composed of a set of local time bitmaps quantifying under what time conditions the job should run, assuming the conditions are evaluated every minute. The maximum scheduling resolution is one minute.

The effective algorithm is to step through every possible minute until the local time matches all the conditions in the time specification. This would result in an algorithm that could take up to 527,040-1 steps to complete, which is far too big a number to evaluate on every job. So, the algorithm optimizes away all impossible cases to make much larger time jumps. For example, if the job cannot be scheduled on the current day, skip over the entire day instead of just the current minute.

There are two ways I approached this task. First, do all calculations in terms of local time, and return the linear time at the last step. Second, do as many calculations as possible in terms of linear time, which can be returned directly. Both methods start with the same input data: The current linear time, and a set of bitmasks representing under what time conditions the job should run.

The first method was the most straightforward to get mostly working, until I started to consider the implications of DST. During the transition from normal time to DST, an hour is skipped. This is no big deal, as mktime will (or can be made to) compensate by picking the next valid hour when presented with a "missing" hour. On the other hand, there are many gotchas when dealing with the duplicated hour. For example, hourly jobs need to get scheduled on both, but daily jobs on only one.

The second method was harder to get working initially, as the math is more complicated. Despite doing many calculations in terms of linear time, this method still needs to keep track of the local time, in order to check against the bitmaps as well as to determine things like when the next day or month should start. This approach proved to be much easier to work with, once the initial math was done, and easier to make work correctly with regards to DST transitions.


[Top] [Contents] [Index] [ ? ]

Footnotes

(1)

There are actually others, but these two are the most simple and portable of the choices. See also http://cr.yp.to/docs/secureipc.html


[Top] [Contents] [Index] [ ? ]

Table of Contents


[Top] [Contents] [Index] [ ? ]

About This Document

This document was generated by Bruce Guenter on June, 8 2005 using texi2html 1.76.

The buttons in the navigation panels have the following meaning:

Button Name Go to From 1.2.3 go to
[ < ] Back previous section in reading order 1.2.2
[ > ] Forward next section in reading order 1.2.4
[ << ] FastBack beginning of this chapter or previous chapter 1
[ Up ] Up up section 1.2
[ >> ] FastForward next chapter 2
[Top] Top cover (top) of document  
[Contents] Contents table of contents  
[Index] Index index  
[ ? ] About about (help)  

where the Example assumes that the current position is at Subsubsection One-Two-Three of a document of the following structure:

  • 1. Section One
    • 1.1 Subsection One-One
      • ...
    • 1.2 Subsection One-Two
      • 1.2.1 Subsubsection One-Two-One
      • 1.2.2 Subsubsection One-Two-Two
      • 1.2.3 Subsubsection One-Two-Three     <== Current Position
      • 1.2.4 Subsubsection One-Two-Four
    • 1.3 Subsection One-Three
      • ...
    • 1.4 Subsection One-Four

This document was generated by Bruce Guenter on June, 8 2005 using texi2html 1.76.

bcron-0.09/bcron.info0000664000076400007640000004617310251704762014111 0ustar bruceguenterThis is bcron.info, produced by makeinfo version 4.8 from bcron.texi. Copyright (C) 2004 Bruce Guenter This document explains ...  File: bcron.info, Node: Top, Next: Introduction, Prev: (dir), Up: (dir) * Menu: * Introduction:: * Design Notes:: * Configuration:: * Implementation Notes::  File: bcron.info, Node: Introduction, Next: Design Notes, Prev: Top, Up: Top 1 Introduction ************** Name comes from: Bruce's / Better / Busy cron. * Menu: * Problems:: * Requirements:: * Design Choices:: * vixie-cron Patches::  File: bcron.info, Node: Problems, Next: Requirements, Prev: Introduction, Up: Introduction 1.1 Problems With Other cron Systems ==================================== * Menu: * Problems with vixie-cron:: * Problems with fcron:: * Problems with anacron:: * Problems with dcron::  File: bcron.info, Node: Problems with vixie-cron, Next: Problems with fcron, Prev: Problems, Up: Problems 1.1.1 vixie-cron ---------------- * Too many bugs The last official release of vixie-cron was in December of 1993. Since then, many security holes and other bugs have been found, requiring every vendor to distribute a multiply-patched version of this system. Most vendors have a different set of patches, making this a bit of a moving target. * Can't handle DST changes vixie-cron's mode of operations is to wake up every minute and determine, with no history, what jobs to run. When DST changes, this causes some jobs to be either skipped or run twice.  File: bcron.info, Node: Problems with fcron, Next: Problems with anacron, Prev: Problems with vixie-cron, Up: Problems 1.1.2 fcron ----------- * Parsing issues If a job starts with a word (terminated by any non-word character, like `/') that happens to also be a username, fcron interprets this word as the userid to run the job under, even for non-root users. This causes problems for jobs like `bin/something' when user `bin' exists. * No support for `/etc/cron.d' * No support for `/etc/crontab' * Gratuitious incompatibilities with vixie-cron Jobs are run with the login shell for the user instead of `/bin/sh'. The default mode of execution prevents the same job from running multiple times (a good option, but bad default). `MAILTO' can only contain a username, not a full email address.  File: bcron.info, Node: Problems with anacron, Next: Problems with dcron, Prev: Problems with fcron, Up: Problems 1.1.3 anacron ------------- Anacron is only really useful for running jobs once a day or less frequently. From what I've seen, it's good at what it does, just not useful at much else.  File: bcron.info, Node: Problems with dcron, Prev: Problems with anacron, Up: Problems 1.1.4 dcron ----------- * No support for `/etc/cron.d' * No support for `/etc/crontab' * No easy way of emulating support for system crontabs dcron's crontab format does not include a "username" column, and as such makes it nearly impossible to even emulate system crontabs. * Unusual or broken handling of DST changes From reading the source, dcron only handles cases where the linear time jumps. When DST changes, linear time does not change, and so dcron effectively does not handle this situation.  File: bcron.info, Node: Requirements, Next: Design Choices, Prev: Problems, Up: Introduction 1.2 Requirements ================ * Security I am choosing to make a number of relatively unorthodox choices in order to avoid many of the security issues that have plagued vixie-cron and other related systems. * External compatability with vixie-cron In particular, the system must support: * crontabs in the same file format as vixie-cron * crontabs submitted via a command-line compatable `crontab' program * mail is sent to `$MAILTO' if the job produces any output (and possibly if the job exits non-zero) * Support for system crontabs This means that the system MUST support: * System crontab entries in `/etc/crontab' * System crontab entries in `/etc/cron.d/*' System crontab entries are additionally differentiated from normal ones by having a "username" column immediately preceding the command. * Intelligent handling of DST time changes One of the biggest frustrations I have had with dealing with vixie-cron is its complete inability to deal with time jumps in an intelligent manner. In particular, when DST changes happen, jobs will either get skipped (when time jumps forward) or executed twice (when time jumps backwards). This is unacceptable. * Allow setting ulimits before executing commands In one of the target installations, we need to set up ulimits (limiting CPU time and memory) before executing commands. It would be easy enough to ulimit the entire daemon, but then the daemon itself would be vulnerable to getting killed when it has run for long enough. Our current setup is to run jobs through a global wrapper script, which can set any necessary limits (or anything else) and then execute the job.  File: bcron.info, Node: Design Choices, Next: vixie-cron Patches, Prev: Requirements, Up: Introduction 1.3 Design Choices ================== Use local socket to submit files. There are two basic methods of submitting crontab files: 1. Use a setuid program to write directly into spool files. 2. Set up a local socket to submit jobs to a daemon. (1) Using a setuid submission agent was discarded to prevent the possibility of all the bugs that have plagued other setuid submission agents. The socket protocol is deliberately very simple, to make the submission agent foolproof. Multiple process daemon. By seperating job submission from job execution, exploiting the system to run arbitrary jobs as privileged users is made even harder. It also makes the design of those individual programs much simpler. ---------- Footnotes ---------- (1) There are actually others, but these two are the most simple and portable of the choices. See also `http://cr.yp.to/docs/secureipc.html'  File: bcron.info, Node: vixie-cron Patches, Prev: Design Choices, Up: Introduction 1.4 vixie-cron Patches ====================== This section lists all the non-trivial patches found for vixie-cron, what problem they appear to address, and (if appropriate) how bcron will avoid the same problem. The patches listed come from multiple sources, including the latest RPM (Fedora Core IIRC). 0days.patch This patch modifies the `crontab.5' man page to remove allowing `0' for day of month or month numbers. badsig.patch On some systems, signal handlers are single-shot. This patch modifies the SIGHUP handler to reactivate itself before it returns. bcron will use the bglibs signal functions, which use sigaction to create suitable handlers, where appropriate. bcron doesn't use signals for any purpose. buffer.patch This patch increases the maximum username length from 20 to 32, and modifies calls to strcpy to use strncpy to ensure all string copies are length bounded. bcron uses dynamically allocated strings to eliminate the possibility of buffer overflows. close_stdin.diff This patch modifies the cron daemon to close stdin, stdout, and stderr on startup, and to reopen them as `/dev/null'. The bcron daemons run under supervise, and have no need of such handling. crond.patch Adds support for `/etc/cron.d' cront_stdin.patch Appears to modify `crontab''s command-line handling such that no argument is interpreted as to read the crontab from standard input. crontab.5.diff Documents several builtin macros to replace the first 5 fields. This macros consist of: `@reboot', `@yearly', `@annually', `@monthly', `@weekly', `@daily', `@midnight', and `@hourly'. bcron will not, at least initially, support these macros. crontab.patch Modifies crontab to use strncpy and snprintf when writing into length-bounded strings. crontabloc.patch Patches the crontab man page to reference `/etc/crontab'. dst.patch Patches the crontab man page to point out that DST may cause jobs to be skipped or repeated. name.patch Appears to modify how the cron daemon handles sending messages to syslog. bcron will log messages to stderr, avoiding syslog entirely. nodot.patch Adds `-i' to the list of arguments sent to sendmail (result is `-FCronDaemon -i -odi -oem'). Only useful for sendmail, but still needed. root_-u-85879.patch Sanity checks the use of `-u' against UID and/or root. security2.patch Does some sanity checking on mailto, and does a setuid before sending mail. bcron plays safe with mailto by putting it into a message header, and always drops root privileges before executing commands. sigchld.patch Return the SIGCHLD handler to its default state before executing commands. sprintf.patch More sprintf -> snprintf conversions. time.patch Sync all the crontabs before sleeping to handle changes in the system time. timeaftertime.patch The previous patch created double execution issues with small backwards adjustments in the clock time.  File: bcron.info, Node: Design Notes, Next: Configuration, Prev: Introduction, Up: Top 2 Design Notes ************** * Menu: * Fundamental Operations:: * Programs:: * Files:: * Inter-Process Communication::  File: bcron.info, Node: Fundamental Operations, Next: Programs, Prev: Design Notes, Up: Design Notes 2.1 Fundamental Operations ========================== The following is a list of all the "core" operations that must be provided based on the requirements. Execute jobs Strictly speaking, this is the only role that requires superuser privileges. Every other job should run as non-root. Schedule jobs Scan all the known jobs, determine which one needs to be executed next, and sleep until that time arrives. Accept new user crontabs Listen for connections on a socket and accept job data. Parse crontabs into internal job format Read the submitted files and parse their contents into a structured format. Check for new system crontabs Check `/etc/crontab' and `/etc/cron.d/*' every minute for modifications. If any files are changed, added, or deleted, add the listed jobs. On systems with tightened security, these files may only be readable by `root'. Manage saved state All jobs need to be saved to disk along with when they were last executed, in order to determine when they should be next executed.  File: bcron.info, Node: Programs, Next: Files, Prev: Fundamental Operations, Up: Design Notes 2.2 Programs ============ `bcron-sched' Top-level scheduler agent. Starts up as root, runs `bcron-exec', and then drops root permanently. `bcron-exec' Accepts jobs to run from `bcron-sched' on stdin, writes exit status back to stdout. `bcron-spool' Manages the cron spool: receives jobs submitted from users, writes them to files in `/var/spool/cron/crontabs', and notifies `bcron-sched'. This needs to be run from `unixserver' in order to securely determine the invoking UID. This program will optionally run the file through an external filter, specified on the command line, before installing the job. `bcron-update' Watches for changes to the system crontabs and notifies `bcron-sched'.  File: bcron.info, Node: Files, Next: Inter-Process Communication, Prev: Programs, Up: Design Notes 2.3 Files ========= * Menu: * File Hierarchy::  File: bcron.info, Node: File Hierarchy, Prev: Files, Up: Files 2.3.1 File Hierarchy -------------------- `/etc/cron.d/' `/etc/cron.d/*' `/etc/crontab' The above three items are read `/var/spool/cron/' `/var/spool/cron/crontabs/' Directory containing raw (text) crontab files. `/var/spool/cron/crontabs/:etc:cron.d:*' Colon is chosen as a seperator because usernames cannot contain colons due to the format of `/etc/passwd'. `/var/spool/cron/crontabs/:etc:crontab' `/var/spool/cron/bcrontabs/' Directory containing pre-parsed (aka compiled) crontab files (Not yet implemented). `/var/spool/cron/tmp/' Temporary directory for files as they are written. `/var/spool/cron/trigger' Named pipe used to tell `bcron-sched' to rescan the crontabs.  File: bcron.info, Node: Inter-Process Communication, Prev: Files, Up: Design Notes 2.4 Inter-Process Communication =============================== All communication between programs is done in terms of either "packets" or "lines". A packet is formatted as a netstring (http://cr.yp.to/proto/netstrings.txt). That is, a packet of length N is encoded as the ASCII decimal value of N, `:', N bytes of data, terminated by `,'. A line is simply a series of non-NUL bytes terminated by a NUL byte. * Menu: * Job Submission Protocol:: * bcron-exec Protocol::  File: bcron.info, Node: Job Submission Protocol, Next: bcron-exec Protocol, Prev: Inter-Process Communication, Up: Inter-Process Communication 2.4.1 Job Submission Protocol ----------------------------- Client sends a packet containing a single byte command followed by the username. If the command requires additional data, it is seperated from the username by a NUL byte. Server responds with a packet containing a response byte followed by a text message. Client command codes are: `S' Submit a user crontab file. The content string contains the entire crontab file. `L' List the current crontab file. No content string. `R' Remove any previously submitted crontab file. No content string. `Y' List all system crontabs. No content string. This command is only available to users `root' and `cron'. Server response codes are: `K' Command was successful; file was parsed and accepted. `D' File could not be parsed. `Z' Temporary internal error.  File: bcron.info, Node: bcron-exec Protocol, Prev: Job Submission Protocol, Up: Inter-Process Communication 2.4.2 bcron-exec Protocol ------------------------- Input packets contain a series of four or more NUL-terminated lines: `ID' `username' `command' `environment' The environment is optional. If the environment contains `SHELL', it replaces the default shell (`/bin/sh'). If the environment contains `MAILTO', it overrides the default mailing address derived from the username. Output packet: `ID' `NUL' `response code' `text message' Output packets are sent asynchronously with respect to input packets.  File: bcron.info, Node: Configuration, Next: Implementation Notes, Prev: Design Notes, Up: Top 3 Configuration *************** * Menu: * Environment Variables::  File: bcron.info, Node: Environment Variables, Prev: Configuration, Up: Configuration 3.1 Environment Variables ========================= `BCRON_SPOOL' The base directory for bcron's files. Defaults to `/var/spool/cron'. `BCRON_USER' The non-root user name to switch to for all processes that don't require root privileges. Defaults to `cron'. `BCRON_MAXSIZE' The maximum size (in bytes) of a single user crontab. Defaults to unlimited. `BCRON_MAXJOBS' The maximum number of jobs in a single user crontab. Defaults to unlimited. `BCRON_SOCKET' The full path to the UNIX-domain socket used to submit crontabs. Defaults to `/var/run/bcron-spool'.  File: bcron.info, Node: Implementation Notes, Prev: Configuration, Up: Top 4 Implementation Notes ********************** * Menu: * Job Scheduler::  File: bcron.info, Node: Job Scheduler, Prev: Implementation Notes, Up: Implementation Notes 4.1 Job Scheduler ================= Getting the job scheduler to work correctly for all possible cases consumed more time than all the other parts of the program put together, and this is *all* because of the problems that daylight savings causes. The ultimate goal is this: given a linear timestamp, determine the *next* linear timestamp after which a job must be run. "Linear time" means the number of seconds since an epoch, in this case the UNIX epoch, midnight GMT January 1st, 1970. To contrast, "local time" means the time in terms of years, months, days, hours, minutes, and seconds in the current locale. The time specification for jobs is composed of a set of local time bitmaps quantifying under what time conditions the job should run, assuming the conditions are evaluated every minute. The maximum scheduling resolution is one minute. The effective algorithm is to step through every possible minute until the local time matches all the conditions in the time specification. This would result in an algorithm that could take up to 527,040-1 steps to complete, which is far too big a number to evaluate on every job. So, the algorithm optimizes away all impossible cases to make much larger time jumps. For example, if the job cannot be scheduled on the current day, skip over the entire day instead of just the current minute. There are two ways I approached this task. First, do all calculations in terms of local time, and return the linear time at the last step. Second, do as many calculations as possible in terms of linear time, which can be returned directly. Both methods start with the same input data: The current linear time, and a set of bitmasks representing under what time conditions the job should run. The first method was the most straightforward to get mostly working, until I started to consider the implications of DST. During the transition from normal time to DST, an hour is skipped. This is no big deal, as `mktime' will (or can be made to) compensate by picking the next valid hour when presented with a "missing" hour. On the other hand, there are many gotchas when dealing with the duplicated hour. For example, hourly jobs need to get scheduled on both, but daily jobs on only one. The second method was harder to get working initially, as the math is more complicated. Despite doing many calculations in terms of linear time, this method still needs to keep track of the local time, in order to check against the bitmaps as well as to determine things like when the next day or month should start. This approach proved to be much easier to work with, once the initial math was done, and easier to make work correctly with regards to DST transitions.  Tag Table: Node: Top143 Node: Introduction309 Node: Problems554 Node: Problems with vixie-cron840 Node: Problems with fcron1555 Node: Problems with anacron2417 Node: Problems with dcron2725 Node: Requirements3364 Node: Design Choices5285 Ref: Design Choices-Footnote-16205 Node: vixie-cron Patches6353 Node: Design Notes9546 Node: Fundamental Operations9763 Node: Programs10952 Node: Files11813 Node: File Hierarchy11969 Node: Inter-Process Communication12766 Node: Job Submission Protocol13331 Node: bcron-exec Protocol14355 Node: Configuration15013 Node: Environment Variables15184 Node: Implementation Notes15896 Node: Job Scheduler16052  End Tag Table bcron-0.09/bcron.texi0000664000076400007640000004727110251704762014127 0ustar bruceguenter\input texinfo @c -*-texinfo-*- @c %**start of header @setfilename bcron.info @settitle bcron documentation @setchapternewpage off @paragraphindent 5 @footnotestyle end @c %**end of header @ifinfo Copyright @copyright{} 2004 Bruce Guenter @end ifinfo @titlepage @title bcron documentation @author Bruce Guenter @subtitle @today{} @end titlepage @ifinfo This document explains ... @end ifinfo @node Top, Introduction, (dir), (dir) @menu * Introduction:: * Design Notes:: * Configuration:: * Implementation Notes:: @end menu @c **************************************************************************** @node Introduction, Design Notes, Top, Top @chapter Introduction Name comes from: Bruce's / Better / Busy cron. @menu * Problems:: * Requirements:: * Design Choices:: * vixie-cron Patches:: @end menu @node Problems, Requirements, Introduction, Introduction @section Problems With Other cron Systems @menu * Problems with vixie-cron:: * Problems with fcron:: * Problems with anacron:: * Problems with dcron:: @end menu @node Problems with vixie-cron, Problems with fcron, Problems, Problems @subsection vixie-cron @itemize @item Too many bugs The last official release of vixie-cron was in December of 1993. Since then, many security holes and other bugs have been found, requiring every vendor to distribute a multiply-patched version of this system. Most vendors have a different set of patches, making this a bit of a moving target. @item Can't handle DST changes vixie-cron's mode of operations is to wake up every minute and determine, with no history, what jobs to run. When DST changes, this causes some jobs to be either skipped or run twice. @end itemize @node Problems with fcron, Problems with anacron, Problems with vixie-cron, Problems @subsection fcron @itemize @item Parsing issues If a job starts with a word (terminated by any non-word character, like @code{/}) that happens to also be a username, fcron interprets this word as the userid to run the job under, even for non-root users. This causes problems for jobs like @code{bin/something} when user @code{bin} exists. @item No support for @file{/etc/cron.d} @item No support for @file{/etc/crontab} @item Gratuitious incompatibilities with vixie-cron Jobs are run with the login shell for the user instead of @file{/bin/sh}. The default mode of execution prevents the same job from running multiple times (a good option, but bad default). @code{MAILTO} can only contain a username, not a full email address. @end itemize @node Problems with anacron, Problems with dcron, Problems with fcron, Problems @subsection anacron Anacron is only really useful for running jobs once a day or less frequently. From what I've seen, it's good at what it does, just not useful at much else. @node Problems with dcron, , Problems with anacron, Problems @subsection dcron @itemize @item No support for @file{/etc/cron.d} @item No support for @file{/etc/crontab} @item No easy way of emulating support for system crontabs dcron's crontab format does not include a ``username'' column, and as such makes it nearly impossible to even emulate system crontabs. @item Unusual or broken handling of DST changes From reading the source, dcron only handles cases where the linear time jumps. When DST changes, linear time does not change, and so dcron effectively does not handle this situation. @end itemize @node Requirements, Design Choices, Problems, Introduction @section Requirements @itemize @item Security I am choosing to make a number of relatively unorthodox choices in order to avoid many of the security issues that have plagued vixie-cron and other related systems. @item External compatability with vixie-cron In particular, the system must support: @itemize @item crontabs in the same file format as vixie-cron @item crontabs submitted via a command-line compatable @file{crontab} program @item mail is sent to @code{$MAILTO} if the job produces any output (and possibly if the job exits non-zero) @end itemize @item Support for system crontabs This means that the system MUST support: @itemize @item System crontab entries in @file{/etc/crontab} @item System crontab entries in @file{/etc/cron.d/*} @end itemize System crontab entries are additionally differentiated from normal ones by having a ``username'' column immediately preceding the command. @item Intelligent handling of DST time changes One of the biggest frustrations I have had with dealing with vixie-cron is its complete inability to deal with time jumps in an intelligent manner. In particular, when DST changes happen, jobs will either get skipped (when time jumps forward) or executed twice (when time jumps backwards). This is unacceptable. @item Allow setting ulimits before executing commands In one of the target installations, we need to set up ulimits (limiting CPU time and memory) before executing commands. It would be easy enough to ulimit the entire daemon, but then the daemon itself would be vulnerable to getting killed when it has run for long enough. Our current setup is to run jobs through a global wrapper script, which can set any necessary limits (or anything else) and then execute the job. @end itemize @node Design Choices, vixie-cron Patches, Requirements, Introduction @section Design Choices @table @asis @item Use local socket to submit files. There are two basic methods of submitting crontab files: @enumerate @item Use a setuid program to write directly into spool files. @item Set up a local socket to submit jobs to a daemon. @end enumerate @footnote{There are actually others, but these two are the most simple and portable of the choices. See also @uref{http://cr.yp.to/docs/secureipc.html}} Using a setuid submission agent was discarded to prevent the possibility of all the bugs that have plagued other setuid submission agents. The socket protocol is deliberately very simple, to make the submission agent foolproof. @item Multiple process daemon. By seperating job submission from job execution, exploiting the system to run arbitrary jobs as privileged users is made even harder. It also makes the design of those individual programs much simpler. @end table @node vixie-cron Patches, , Design Choices, Introduction @section vixie-cron Patches This section lists all the non-trivial patches found for vixie-cron, what problem they appear to address, and (if appropriate) how bcron will avoid the same problem. The patches listed come from multiple sources, including the latest RPM (Fedora Core IIRC). @table @asis @item 0days.patch This patch modifies the @file{crontab.5} man page to remove allowing @samp{0} for day of month or month numbers. @item badsig.patch On some systems, signal handlers are single-shot. This patch modifies the SIGHUP handler to reactivate itself before it returns. bcron will use the bglibs signal functions, which use sigaction to create suitable handlers, where appropriate. bcron doesn't use signals for any purpose. @c uses named pipe triggers @c instead of signals to deliver notifications, removing the need for @c most signal handlers. @item buffer.patch This patch increases the maximum username length from 20 to 32, and modifies calls to strcpy to use strncpy to ensure all string copies are length bounded. bcron uses dynamically allocated strings to eliminate the possibility of buffer overflows. @item close_stdin.diff This patch modifies the cron daemon to close stdin, stdout, and stderr on startup, and to reopen them as @file{/dev/null}. The bcron daemons run under supervise, and have no need of such handling. @item crond.patch Adds support for @file{/etc/cron.d} @item cront_stdin.patch Appears to modify @file{crontab}'s command-line handling such that no argument is interpreted as to read the crontab from standard input. @item crontab.5.diff Documents several builtin macros to replace the first 5 fields. This macros consist of: @samp{@@reboot}, @samp{@@yearly}, @samp{@@annually}, @samp{@@monthly}, @samp{@@weekly}, @samp{@@daily}, @samp{@@midnight}, and @samp{@@hourly}. bcron will not, at least initially, support these macros. @item crontab.patch Modifies crontab to use strncpy and snprintf when writing into length-bounded strings. @item crontabloc.patch Patches the crontab man page to reference @file{/etc/crontab}. @item dst.patch Patches the crontab man page to point out that DST may cause jobs to be skipped or repeated. @item name.patch Appears to modify how the cron daemon handles sending messages to syslog. bcron will log messages to stderr, avoiding syslog entirely. @item nodot.patch Adds @samp{-i} to the list of arguments sent to sendmail (result is @samp{-FCronDaemon -i -odi -oem}). Only useful for sendmail, but still needed. @item root_-u-85879.patch Sanity checks the use of @samp{-u} against UID and/or root. @item security2.patch Does some sanity checking on mailto, and does a setuid before sending mail. bcron plays safe with mailto by putting it into a message header, and always drops root privileges before executing commands. @item sigchld.patch Return the SIGCHLD handler to its default state before executing commands. @item sprintf.patch More sprintf -> snprintf conversions. @item time.patch Sync all the crontabs before sleeping to handle changes in the system time. @item timeaftertime.patch The previous patch created double execution issues with small backwards adjustments in the clock time. @end table @c **************************************************************************** @node Design Notes, Configuration, Introduction, Top @chapter Design Notes @menu * Fundamental Operations:: * Programs:: * Files:: * Inter-Process Communication:: @end menu @node Fundamental Operations, Programs, Design Notes, Design Notes @section Fundamental Operations The following is a list of all the ``core'' operations that must be provided based on the requirements. @table @asis @item Execute jobs Strictly speaking, this is the only role that requires superuser privileges. Every other job should run as non-root. @item Schedule jobs Scan all the known jobs, determine which one needs to be executed next, and sleep until that time arrives. @item Accept new user crontabs Listen for connections on a socket and accept job data. @item Parse crontabs into internal job format Read the submitted files and parse their contents into a structured format. @item Check for new system crontabs Check @file{/etc/crontab} and @file{/etc/cron.d/*} every minute for modifications. If any files are changed, added, or deleted, add the listed jobs. On systems with tightened security, these files may only be readable by @samp{root}. @item Manage saved state All jobs need to be saved to disk along with when they were last executed, in order to determine when they should be next executed. @end table @node Programs, Files, Fundamental Operations, Design Notes @section Programs @table @file @item bcron-sched Top-level scheduler agent. Starts up as root, runs @file{bcron-exec}, and then drops root permanently. @item bcron-exec Accepts jobs to run from @file{bcron-sched} on stdin, writes exit status back to stdout. @item bcron-spool Manages the cron spool: receives jobs submitted from users, writes them to files in @file{/var/spool/cron/crontabs}, and notifies @file{bcron-sched}. This needs to be run from @file{unixserver} in order to securely determine the invoking UID. This program will optionally run the file through an external filter, specified on the command line, before installing the job. @item bcron-update Watches for changes to the system crontabs and notifies @file{bcron-sched}. @end table @node Files, Inter-Process Communication, Programs, Design Notes @section Files @menu * File Hierarchy:: @end menu @node File Hierarchy, , Files, Files @subsection File Hierarchy @table @file @item /etc/cron.d/ @item /etc/cron.d/* @item /etc/crontab The above three items are read @item /var/spool/cron/ @item /var/spool/cron/crontabs/ Directory containing raw (text) crontab files. @item /var/spool/cron/crontabs/:etc:cron.d:* Colon is chosen as a seperator because usernames cannot contain colons due to the format of @file{/etc/passwd}. @item /var/spool/cron/crontabs/:etc:crontab @item /var/spool/cron/bcrontabs/ Directory containing pre-parsed (aka compiled) crontab files (Not yet implemented). @item /var/spool/cron/tmp/ Temporary directory for files as they are written. @item /var/spool/cron/trigger Named pipe used to tell @file{bcron-sched} to rescan the crontabs. @end table @ignore @subsection Compiled File Format A compled data file is composed of zero or more sections. Each section is composed of a length number followed by that many bytes of data. The data is stored in a binary format to allow certain numbers in the compiled file (most notably the last execution time) to be rewritten in-place. The first byte of the data indicates what kind of section it is. @table @code @item 1 Standard job, contains: @itemize @item Time of last execution @item Options bitmap (32 bits) @item Minute bitmap (60 of 64 bits) @item Hour bitmap (24 of 32 bits) @item Day-of-month bitmap (31 of 32 bits) @item Month bitmap (12 of 32 bits) @item Day-of-week bitmap (7 of 32 bits) @item Run-as username (NUL terminated) @item Command (NUL terminated) @item Environment assignments @end itemize @end table Options: @itemize @item SERIALIZE Jobs with this option bit set will not be run if the same job is already running. @end itemize Note: All numbers are in 32-bit LSB format unless otherwise indicated. Timestamps represented as the number of minutes since the Epoch (00:00:00 UTC, January 1, 1970). @end ignore @node Inter-Process Communication, , Files, Design Notes @section Inter-Process Communication All communication between programs is done in terms of either ``packets'' or ``lines''. A packet is formatted as a @uref{http://cr.yp.to/proto/netstrings.txt,netstring}. That is, a packet of length @var{N} is encoded as the ASCII decimal value of @var{N}, @samp{:}, N bytes of data, terminated by @samp{,}. A line is simply a series of non-NUL bytes terminated by a NUL byte. @menu * Job Submission Protocol:: * bcron-exec Protocol:: @end menu @node Job Submission Protocol, bcron-exec Protocol, Inter-Process Communication, Inter-Process Communication @subsection Job Submission Protocol Client sends a packet containing a single byte command followed by the username. If the command requires additional data, it is seperated from the username by a NUL byte. Server responds with a packet containing a response byte followed by a text message. Client command codes are: @table @code @item S Submit a user crontab file. The content string contains the entire crontab file. @item L List the current crontab file. No content string. @item R Remove any previously submitted crontab file. No content string. @item Y List all system crontabs. No content string. This command is only available to users @samp{root} and @samp{cron}. @end table Server response codes are: @table @code @item K Command was successful; file was parsed and accepted. @item D File could not be parsed. @item Z Temporary internal error. @end table @node bcron-exec Protocol, , Job Submission Protocol, Inter-Process Communication @subsection bcron-exec Protocol Input packets contain a series of four or more NUL-terminated lines: @table @code @item ID @item username @item command @item environment The environment is optional. If the environment contains @code{SHELL}, it replaces the default shell (@samp{/bin/sh}). If the environment contains @code{MAILTO}, it overrides the default mailing address derived from the username. @end table Output packet: @table @code @item ID @item NUL @item response code @item text message @end table Output packets are sent asynchronously with respect to input packets. @c **************************************************************************** @node Configuration, Implementation Notes, Design Notes, Top @chapter Configuration @menu * Environment Variables:: @end menu @node Environment Variables, , Configuration, Configuration @section Environment Variables @table @code @item BCRON_SPOOL The base directory for bcron's files. Defaults to @file{/var/spool/cron}. @item BCRON_USER The non-root user name to switch to for all processes that don't require root privileges. Defaults to @samp{cron}. @item BCRON_MAXSIZE The maximum size (in bytes) of a single user crontab. Defaults to unlimited. @item BCRON_MAXJOBS The maximum number of jobs in a single user crontab. Defaults to unlimited. @item BCRON_SOCKET The full path to the UNIX-domain socket used to submit crontabs. Defaults to @file{/var/run/bcron-spool}. @end table @c **************************************************************************** @node Implementation Notes, , Configuration, Top @chapter Implementation Notes @menu * Job Scheduler:: @end menu @node Job Scheduler, , Implementation Notes, Implementation Notes @section Job Scheduler Getting the job scheduler to work correctly for all possible cases consumed more time than all the other parts of the program put together, and this is @strong{all} because of the problems that daylight savings causes. The ultimate goal is this: given a linear timestamp, determine the @strong{next} linear timestamp after which a job must be run. ``Linear time'' means the number of seconds since an epoch, in this case the UNIX epoch, midnight GMT January 1st, 1970. To contrast, ``local time'' means the time in terms of years, months, days, hours, minutes, and seconds in the current locale. The time specification for jobs is composed of a set of local time bitmaps quantifying under what time conditions the job should run, assuming the conditions are evaluated every minute. The maximum scheduling resolution is one minute. The effective algorithm is to step through every possible minute until the local time matches all the conditions in the time specification. This would result in an algorithm that could take up to 527,040-1 steps to complete, which is far too big a number to evaluate on every job. So, the algorithm optimizes away all impossible cases to make much larger time jumps. For example, if the job cannot be scheduled on the current day, skip over the entire day instead of just the current minute. There are two ways I approached this task. First, do all calculations in terms of local time, and return the linear time at the last step. Second, do as many calculations as possible in terms of linear time, which can be returned directly. Both methods start with the same input data: The current linear time, and a set of bitmasks representing under what time conditions the job should run. The first method was the most straightforward to get mostly working, until I started to consider the implications of DST. During the transition from normal time to DST, an hour is skipped. This is no big deal, as @code{mktime} will (or can be made to) compensate by picking the next valid hour when presented with a ``missing'' hour. On the other hand, there are many gotchas when dealing with the duplicated hour. For example, hourly jobs need to get scheduled on both, but daily jobs on only one. The second method was harder to get working initially, as the math is more complicated. Despite doing many calculations in terms of linear time, this method still needs to keep track of the local time, in order to check against the bitmaps as well as to determine things like when the next day or month should start. This approach proved to be much easier to work with, once the initial math was done, and easier to make work correctly with regards to DST transitions. @c **************************************************************************** @contents @bye bcron-0.09/README0000664000076400007640000000503110251704762012775 0ustar bruceguenterbcron Short Description Bruce Guenter Version 0.09 2005-06-08 This is bcron, a new cron system designed with secure operations in mind. To do this, the system is divided into several seperate programs, each responsible for a seperate task, with strictly controlled communications between them. The user interface is a drop-in replacement for similar systems (such as vixie-cron), but the internals differ greatly. A mailing list has been set up to discuss this and other packages. To subscribe, send an email to: bgware-subscribe@lists.untroubled.org A mailing list archive is available at: http://lists.untroubled.org/?list=bgware Development versions of bcron are available via Subversion at: svn://bruce-guenter.dyndns.org/bcron/trunk Requirements: - Needs ucspi-unix (or ucspi-local) to be able to accept user-submitted crontabs. - bglibs version 1.021 or later from http://untroubled.org/bglibs/ Installation: - Build the sources by running "make" - Run the "instshow" command to see what will be installed (optional). - After the package has been compiled, run the following command as root: make install This command will produce no output if there are no errors. Configuration: - Create a system user and group "cron". This user will own all the crontab files. - Create the spool directory as follows: mkdir -p /var/spool/cron/crontabs /var/spool/cron/tmp mkfifo /var/spool/cron/trigger for i in crontabs tmp trigger; do chown cron:cron /var/spool/cron/$i chmod go-rwx /var/spool/cron/$i done - Create the configuration directory /etc/bcron as follows: mkdir -p /etc/bcron You can put any common configuration settings into this directory, like alternate spool directories in BCRON_SPOOL. Operation: - The scheduler can be started with the following command: envdir /etc/bcron bcron-start | multilog t /var/log/bcron & - To accept crontabs from users, run the spooler: envdir /etc/bcron \ envuidgid cron \ unixserver -U /var/run/bcron-spool \ bcron-spool >/dev/null 2>&1 & - To update system crontabs, run the updater: envdir /etc/bcron \ bcron-update /etc/crontab /etc/cron.d >/dev/null 2>&1 & - Sample run scripts are provided for operating the above commands under svscan and supervise (or equivalents such as runit). This package is Copyright(C) 2005 Bruce Guenter or FutureQuest, Inc., and may be copied according to the GNU GENERAL PUBLIC LICENSE (GPL) Version 2 or a later version. A copy of this license is included with this package. This package comes with no warranty of any kind. bcron-0.09/TARGETS0000664000076400007640000000070410251704762013153 0ustar bruceguenterall bcron-exec bcron-exec.o bcron-sched bcron-sched.o bcron-spool bcron-spool.o bcron-start bcron-start.o bcron-update bcron-update.o bcron.a bcron.html bcron.info bcrontab bcrontab.o chdir.o clean clean-spac compile conf_bin.c connection.o crontab.o crontabs.o docs install installer instcheck insthier.o instshow job.o libraries load makelib ministat.o programs sendpacket.o tempfile.o test-sched test-sched.o time.o timespec-next.o timespec-parse.o bcron-0.09/timespec-next.c0000664000076400007640000001252610251704762015055 0ustar bruceguenter#include #include #include #include #include #include #include #include #include #include #include #include "bcron.h" static int days_in_month(int month, int year) { switch (month) { case 1: /* February is special */ return ((year % 4) != 0) ? 28 : ((year % 100) != 0) ? 29 : ((year % 400) != 0) ? 28 : 29; case 3: /* April */ case 5: /* June */ case 8: /* September */ case 10: /* November */ return 30; default: /* All the others have 31 */ return 31; } } static time_t fixup_hour(time_t t, struct tm* tm) { *tm = *(localtime(&t)); /* From 13..23: The hour underflowed */ if (tm->tm_hour > 12) { t += (24 - tm->tm_hour) * 60 * 60; return fixup_hour(t, tm); } /* From 0..12: The hour overflowed */ else if (tm->tm_hour > 0) { t -= tm->tm_hour * 60 * 60; tm->tm_hour = 0; } return t; } static void advance_tmmonth(struct tm* tm) { tm->tm_sec = tm->tm_min = tm->tm_hour = 0; tm->tm_wday = (tm->tm_wday + days_in_month(tm->tm_mon, tm->tm_year) - (tm->tm_mday - 1)) % 7; tm->tm_mday = 1; if (++tm->tm_mon >= 12) { tm->tm_mon = 0; ++tm->tm_year; } } static void advance_tmday(struct tm* tm) { tm->tm_sec = tm->tm_min = tm->tm_hour = 0; tm->tm_wday = (tm->tm_wday + 1) % 7; if (++tm->tm_mday > days_in_month(tm->tm_mon, tm->tm_year)) advance_tmmonth(tm); } static void advance_tmhour(struct tm* tm) { tm->tm_sec = tm->tm_min = 0; if (++tm->tm_hour >= 24) advance_tmday(tm); } static time_t advance_month(time_t t, struct tm* tm) { t += (days_in_month(tm->tm_mon, tm->tm_year) - (tm->tm_mday - 1)) * 24*60*60 - tm->tm_hour * 60*60 - tm->tm_min*60 - tm->tm_sec; if (daylight) t = fixup_hour(t, tm); else advance_tmmonth(tm); return t; } static time_t advance_day(time_t t, struct tm* tm) { t += 24*60*60 - tm->tm_hour*60*60 - tm->tm_min*60 - tm->tm_sec; if (daylight) t = fixup_hour(t, tm); else advance_tmday(tm); return t; } static time_t advance_hour(time_t t, struct tm* tm, int daily) { const int prevhour = tm->tm_hour; const int wasisdst = tm->tm_isdst; t += 60*60 - tm->tm_min * 60 - tm->tm_sec; if (daylight) { *tm = *(localtime(&t)); /* Run the skipped hour twice. * * case1 example: entering repeated hour * before: 1081058400 2004-04-04 01:00:00 EST * after: 1081062000 2004-04-04 03:00:00 EDT * want: 1081062000 2004-04-04 02:00:00 EDT * * case2 example: exiting repeated hour * before: 1081062000 2004-04-04 02:00:00 EDT * after: 1081065600 2004-04-04 04:00:00 EDT * want: 1081062000 2004-04-04 03:00:00 EDT */ if (tm->tm_hour > prevhour + 1) { tm->tm_hour = prevhour + 1; if (wasisdst) t -= 60*60; } else if (daily && tm->tm_hour == prevhour) return advance_hour(t, tm, 0); } else advance_tmhour(tm); return t; } static time_t advance_minute(time_t t, struct tm* tm, int daily) { t += 60 - tm->tm_sec; tm->tm_sec = 0; if (++tm->tm_min >= 60) t = advance_hour(t, tm, daily); return t; } /** Calculate the next time this job should get run. The algorithm used here is very simple: start at the last time the job was run (or the current time if it hasn't been run yet), and scan forward effectively minute-by-minute until a time that matches the specified times is found. The scan is intentionally limited to one year, since no job can be specified more than that much in the future. Obviously, scanning every minute for a year for every job is far too much work, so the scan is optimized by advancing over larger intervals if no job is possible during that interval. For example, if a job is specified to run only on the 1st of the month, the loop advances whole days at a time until the month day equals 1. This means the loop will complete in a maximum of 113 steps (12-1 months + 31-1 days + 24-1 hours + 60-1 minutes). Also, the more frequently a job needs to be scheduled, the fewer steps are required to calculate its next time. My first attempt at this loop calculated exclusively using broken down time. However, using that method, it was impossible to both (1) prevent jobs from running twice on the duplicated DST hour and (2) still run jobs that are supposed to run during the duplicated DST hour. */ time_t timespec_next(const struct job_timespec* ts, time_t start, const struct tm* starttm) { time_t t; struct tm tm; time_t timelimit; const int daily = ts->hour_count <= 1; tm = *starttm; t = start - tm.tm_sec; tm.tm_sec = 0; timelimit = start + 366*24*60*60 * 10; do { assert(tm.tm_sec == 0); if ((ts->months & (1ULL << tm.tm_mon)) != 0) if ((ts->mdays & (1ULL << tm.tm_mday)) != 0 && (ts->wdays & (1ULL << tm.tm_wday)) != 0) if ((ts->hours & (1ULL << tm.tm_hour)) != 0) if ((ts->minutes & (1ULL << tm.tm_min)) != 0) return t; else t = advance_minute(t, &tm, daily); else { t = advance_hour(t, &tm, daily); } else t = advance_day(t, &tm); else t = advance_month(t, &tm); } while (t < timelimit); return INT_MAX; } void timespec_next_init(void) { /* This sets daylight, which is used in calculating times. */ tzset(); debug4(DEBUG_SCHED, "Timezone: ", tzname[0], " alt: ", tzname[1]); } bcron-0.09/bcron-sched.80000664000076400007640000000123010251704762014372 0ustar bruceguenter.TH bcron-sched 8 .SH NAME bcron-sched \- bcron system scheduler .SH SYNOPSIS .B bcron-sched .SH DESCRIPTION .B bcron-sched reads job descriptions from stored crontabs, determines when they need to be run, and writes execution commands to standard output for .B bcron-exec to execute. .P .B bcron-sched should normally be run by .B bcron-start in conjunction with .BR bcron-exec . .SH ENVIRONMENT .TP 5 .B BCRON_SPOOL The spool directory for bcron. Defaults to .IR /var/spool/cron . .SH FILES .I /var/spool/cron/crontabs/crontabs .I /var/spool/cron/crontabs/trigger .SH SEE ALSO bcron-exec(8), bcron-start(8), bcrontab(1) .SH AUTHOR Bruce Guenter bcron-0.09/bcron-sched.c0000664000076400007640000000531610251704762014456 0ustar bruceguenter#include #include #include #include #include #include #include #include #include #include #include #include #include "bcron.h" const char program[] = "bcron-sched"; const int msg_show_pid = 0; int msg_debug_bits = 0; static iopoll_fd ios[3]; /* Fill in tm and calculate the top of the next minute. */ static time_t next_minute(time_t t, struct tm* tm) { t += 60; *tm = *(localtime(&t)); t -= tm->tm_sec; tm->tm_sec = 0; return t; } static time_t nexttime; static struct tm nexttm; static struct timeval reftime; static void calctimes(void) { struct ghashiter i; struct job* job; nexttime = next_minute(reftime.tv_sec, &nexttm); /* Make sure all jobs have a time scheduled */ ghashiter_loop(&i, &crontabs) { for (job = ((struct crontabs_entry*)i.entry)->data.jobs; job != 0; job = job->next) { if (job->nexttime == 0) job->nexttime = timespec_next(&job->times, nexttime, &nexttm); } } } static void loadall(void) { gettimeofday(&reftime, 0); crontabs_load(); calctimes(); } static long calcinterval(void) { long interval; gettimeofday(&reftime, 0); interval = (nexttime - reftime.tv_sec) * 1000 - reftime.tv_usec / 1000; debug2(DEBUG_SCHED, "Interval: ", utoa(interval)); /* Add a little "fudge" to the interval, as the select frequently returns a jiffy before the interval would actually expire. */ return interval + 100; } static void run_jobs(void) { struct ghashiter i; gettimeofday(&reftime, 0); if (reftime.tv_sec < nexttime) return; debug2(DEBUG_SCHED, "Running jobs at: ", fmttime(reftime.tv_sec)); ghashiter_loop(&i, &crontabs) { struct job* job; for (job = ((struct crontabs_entry*)i.entry)->data.jobs; job != 0; job = job->next) if (job->nexttime <= reftime.tv_sec) { job_exec(job); job->nexttime = 0; } } loadall(); } #ifndef IGNORE_EXEC static void handle_stdin(void) { char buf[256]; read(0, buf, sizeof buf); } #endif int main(void) { msg_debug_init(); chdir_bcron(); nonblock_on(0); timespec_next_init(); trigger_set(ios, TRIGGER); #ifndef IGNORE_EXEC ios[2].fd = 0; ios[2].events = IOPOLL_READ; #endif msg1("Starting"); crontabs_init(&crontabs); loadall(); for (;;) { switch (iopoll(ios, 3, calcinterval())) { case -1: if (errno == EAGAIN || errno == EINTR) continue; die1sys(111, "poll failed"); case 0: run_jobs(); continue; default: if (trigger_pulled(ios)) { trigger_set(ios, TRIGGER); loadall(); } #ifndef IGNORE_EXEC if (ios[2].revents) handle_stdin(); #endif } } } bcron-0.09/sendpacket.c0000664000076400007640000000103710251704762014404 0ustar bruceguenter#include #include #include #include "bcron.h" static str packet; int sendpacket(int fd, const str* s) { const char* ptr; long wr; long len; packet.len = 0; if (!str_catu(&packet, s->len) || !str_catc(&packet, ':') || !str_cat(&packet, s) || !str_catc(&packet, ',')) { errno = ENOMEM; return -1; } len = packet.len; ptr = packet.s; while (len > 0) { if ((wr = write(fd, ptr, len)) <= 0) return wr; len -= wr; ptr += wr; } return packet.len; } bcron-0.09/bcrontab.10000664000076400007640000000262110251704762013773 0ustar bruceguenter.TH bcrontab 1 .SH NAME bcrontab \- Manage users crontab files .SH SYNOPSIS .B bcrontab [ .B -u user ] .I file .P .B bcrontab [ .B -u user ] { .B -l | .B -r | .B -e } .SH DESCRIPTION .B bcrontab interfaces with the .B bcron-spool daemon to manage crontab files in the privileged spool directory. .SH OPTIONS .TP .B -u user Tell .B bcron-spool that we are acting on behalf of the named user. .B bcron-spool will only accept the username if .B bcrontab is running as either root or the same user ID as the named user. .TP .B -l List the cronab crontab to standard output. .TP .B -r Remove the user's crontab. .TP .B -e Edit the current crontab. .SH ENVIRONMENT .TP .B VISUAL If this is set, it is used as the editor to invoke to edit a crontab. .TP .B EDITOR If .B $VISUAL is not set and this is, it is used as the editor to invoke to edit a crontab. If neither are set, .I /bin/vi is used. .TP .B BCRON_SOCKET The path to the named socket used to communicate with .BR bcron-spool . Defaults to .IR /var/run/bcron-spool . .TP .B LOGNAME .TP .B USER These two variables are used, in order, to determine the user name invoking the program. One must be set if the .B -u option is not used. .SH FILES .B bcrontab tries to writes a temporary file into the current directory, and then into .I /tmp if that fails, in order to edit the current crontab. .SH SEE ALSO bcron-spool(8), crontab(5) .SH AUTHOR Bruce Guenter bcron-0.09/bcrontab.c0000664000076400007640000001302110251704762014051 0ustar bruceguenter#include #include #include #include #include #include #include #include #include #include #include #include "bcron.h" static const char* user = 0; static int cmd_list = 0; static int cmd_remove = 0; static int cmd_edit = 0; static int cmd_listsys = 0; const char program[] = "bcrontab"; const int msg_show_pid = 0; const char cli_help_prefix[] = "\n"; const char cli_help_suffix[] = ""; const char cli_args_usage[] = "[ -u user ] file\n" " or: bcrontab [ -u user ] { -l | -r | -e }"; const int cli_args_min = 0; const int cli_args_max = 1; cli_option cli_options[] = { // { ch, name, type, value, ptr, help, default }, // type is: // CLI_FLAG, // CLI_COUNTER, // CLI_INTEGER, // CLI_UINTEGER, // CLI_STRING, // CLI_STRINGLIST, // CLI_FUNCTION, // CLI_SEPARATOR, /* The following four options are required for compatability */ { 'u', "user", CLI_STRING, 0, &user, "Specify the user name", 0 }, { 'l', "list", CLI_FLAG, 1, &cmd_list, "List user's current crontab", 0 }, { 'r', "remove", CLI_FLAG, 1, &cmd_remove, "Remove user's current crontab", 0 }, { 'y', "system", CLI_FLAG, 1, &cmd_listsys, "List all system crontabs", 0 }, { 'e', "edit", CLI_FLAG, 1, &cmd_edit, "Edit user's current crontab", 0 }, /* Extended options */ {0,0,0,0,0,0,0} }; static int do_connect(void) { const char* sockpath; int sock; if ((sockpath = getenv("BCRON_SOCKET")) == 0) sockpath = SOCKET_PATH; if ((sock = socket_unixstr()) == -1) die1sys(111, "Could not create socket"); if (!socket_connectu(sock, sockpath)) die1sys(111, "Could not connect to bcron-spool"); return sock; } static str packet; static int docmd(char cmd, str* data, int softfail) { int sock; ibuf sockbuf; packet.len = 0; wrap_str(str_catc(&packet, cmd)); wrap_str(str_cats(&packet, user)); if (data) { wrap_str(str_catc(&packet, 0)); wrap_str(str_cat(&packet, data)); } sock = do_connect(); if (!ibuf_init(&sockbuf, sock, 0, IOBUF_NEEDSCLOSE, 0)) die1sys(111, "Could not initialize socket buffer"); if (sendpacket(sock, &packet) <= 0) die1sys(111, "Could not send command to server"); if (!ibuf_getnetstring(&sockbuf, &packet)) die1sys(111, "Could not read response from server"); ibuf_close(&sockbuf); if ((!softfail && packet.s[0] == 'D') || packet.s[0] == 'Z') die1(111, packet.s+1); return packet.s[0] != 'D'; } static int do_list(void) { docmd('L', 0, 0); return write(1, packet.s + 1, packet.len - 1) != (long)(packet.len - 1); } static int do_remove(void) { docmd('R', 0, 0); return 0; } static int do_replace(const char* path) { static str file; if (!ibuf_openreadclose(path, &file)) die3sys(111, "Could not read '", path, "'"); docmd('S', &file, 0); msg1("crontab saved successfully"); return 0; } static void edit(const char* path) { const char* editor; int pid; static str cmd; char* argv[4]; int status; if ((editor = getenv("VISUAL")) == 0 && (editor = getenv("EDITOR")) == 0) editor = "vi"; if (!str_copys(&cmd, editor) || !str_cats(&cmd, " '") || !str_cats(&cmd, path) || !str_catc(&cmd, '\'')) { unlink(path); die_oom(111); } argv[0] = "/bin/sh"; argv[1] = "-c"; argv[2] = cmd.s; argv[3] = 0; if ((pid = fork()) == -1) { unlink(path); die1sys(111, "Could not fork editor"); } if (pid == 0) { /* Run editor in child */ execv(argv[0], argv); die1sys(111, "Could not execute /bin/sh"); } if (waitpid(pid, &status, 0) == -1) { unlink(path); die1sys(111, "Internal error"); } if (!WIFEXITED(status)) { unlink(path); die1(111, "Editor crashed"); } if (WEXITSTATUS(status) != 0) { unlink(path); exit(WEXITSTATUS(status)); } } static int do_edit(void) { str file = {0,0,0}; int fd; if ((fd = tempfile("bcrontab")) == -1 && (fd = tempfile("/tmp/bcrontab")) == -1) die1sys(111, "Could not create temporary file"); if (!docmd('L', 0, 1)) wrap_str(str_copys(&packet, "K")); if (write(fd, packet.s + 1, packet.len - 1) != (long)(packet.len - 1)) die1sys(111, "Could not copy crontab to temporary file"); close(fd); edit(tempname.s); /* Re-open the temporary file here because some editors will move the original file aside before writing the new one. */ if (!ibuf_openreadclose(tempname.s, &file)) die1sys(111, "Could not re-read temporary file"); if (file.len == packet.len - 1 && memcmp(file.s, packet.s + 1, file.len) == 0) msg1("no changes made to crontab"); else docmd('S', &file, 0); return 0; } static int do_listsys(void) { docmd('Y', 0, 0); return write(1, packet.s + 1, packet.len - 1) != (long)(packet.len - 1); } int cli_main(int argc, char* argv[]) { if (user == 0 && (user = getenv("LOGNAME")) == 0 && (user = getenv("USER")) == 0) die1(111, "Could not determine user name"); switch (cmd_list + cmd_remove + cmd_edit + cmd_listsys) { case 0: if (argc != 1) usage(111, "Too few command-line arguments"); break; case 1: if (argc != 0) usage(111, "Too many command-line arguments"); break; default: usage(111, "You must specify only one of -l, -r, -e, or -y"); } if (cmd_list) return do_list(); else if (cmd_remove) return do_remove(); else if (cmd_edit) return do_edit(); else if (cmd_listsys) return do_listsys(); else /* cmd_replace */ return do_replace(argv[0]); } bcron-0.09/bcron-sched.run0000664000076400007640000000007310251704762015033 0ustar bruceguenter#!/bin/sh exec 2>&1 exec \ envdir /etc/bcron \ bcron-start bcron-0.09/bcron-spool.run0000664000076400007640000000023610251704762015102 0ustar bruceguenter#!/bin/sh exec >/dev/null 2>&1 exec \ envdir /etc/bcron \ envuidgid cron \ sh -c ' exec \ unixserver -U ${BCRON_SOCKET:-/var/run/bcron-spool} \ bcron-spool ' bcron-0.09/tests.sh0000664000076400007640000003113510251704762013617 0ustar bruceguenter#!/bin/sh src=`pwd` tmp=$src/tests-tmp rm -rf $tmp mkdir -p $tmp PATH="$src:/bin:/usr/bin:/usr/local/bin" tests_failed=0 tests_count=0 _UID=`id -u` _GID=`id -g` export USER=`whoami` export BCRON_SPOOL=$tmp/spool export BCRON_SPOOL=$tmp/spool export BCRON_USER=$USER export BCRONTAB_SOCKET=$tmp/socket export CRONTABS=$BCRON_SPOOL/crontabs mkdir $BCRON_SPOOL mkdir $CRONTABS mkdir $BCRON_SPOOL/tmp mkfifo $BCRON_SPOOL/trigger netstring() { perl -e ' $string = join("", <>); printf "%d:%s,", length($string), $string; ' } spoolsockcmd() { unixclient $BCRONTAB_SOCKET sh -c " perl -e ' \$string = join(\"\", <>); printf \"%d:%s,\", length(\$string), \$string; ' >&7; cat <&6; echo" } spoolcmd() { perl -e ' $string = join("", <>); printf "%d:%s,", length($string), $string; ' | env PROTO=UNIX UNIXREMOTEEUID=`id -u` $src/bcron-spool "$@" 2>/dev/null echo } sched() { MSG_DEBUG=0 $src/test-sched "$@" 2>&1 } run_compare_test() { local name=$1 shift sed -e "s:@SOURCE@:$src:g" -e "s:@TMPDIR@:$tmp:g" -e "s:@UID@:$_UID:" -e "s:@GID@:$_GID:" >$tmp/expected ( runtest "$@" 2>&1 ) 2>&1 >$tmp/actual-raw cat -v $tmp/actual-raw >$tmp/actual if ! cmp $tmp/expected $tmp/actual >/dev/null 2>&1 then echo "Test $name $@ failed:" ( cd $tmp; diff -U 9999 expected actual | tail -n +3; echo; ) let tests_failed=tests_failed+1 fi rm -f $tmp/expected $tmp/actual let tests_count=tests_count+1 } ##### Test tests/spool-delete-baduser ##### runtest() { touch $CRONTABS/root echo -n "Rroot" | spoolcmd test -e $CRONTABS/root && echo File still exists. } run_compare_test tests/spool-delete-baduser <'' specifies skips of the number's value through the range. For example, ``0-23/2'' can be used in the hours field to specify command execution every other hour (the alternative in the V7 standard is ``0,2,4,6,8,10,12,14,16,18,20,22''). Steps are also permitted after an asterisk, so if you want to say ``every two hours'', just use ``*/2''. .PP Names can also be used for the ``month'' and ``day of week'' fields. Use the first three letters of the particular day or month (case doesn't matter). Ranges or lists of names are not allowed. .PP The ``sixth'' field (the rest of the line) specifies the command to be run. The entire command portion of the line will be executed by /bin/sh or by the shell specified in the SHELL variable of the cronfile. .PP Note: The day of a command's execution can be specified by two fields \(em day of month, and day of week. If both fields are restricted (ie, aren't *), the command will be run when .I either field matches the current time. For example, .br ``30 4 1,15 * 5'' would cause a command to be run at 4:30 am on the 1st and 15th of each month, plus every Friday. .SH EXAMPLE CRON FILE .nf # use /bin/sh to run commands, no matter what /etc/passwd says SHELL=/bin/sh # mail any output to `bruce@example.com', no matter whose crontab this is MAILTO=bruce@example.com # # run five minutes after midnight, every day 5 0 * * * $HOME/bin/daily.job >> $HOME/tmp/out 2>&1 # run at 2:15pm on the first of every month -- output mailed to bruce (above) 15 14 1 * * $HOME/bin/monthly 23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday" 5 4 * * sun echo "run at 5 after 4 every sunday" .fi .SH FILES .TP 20 .I /etc/crontab System crontab file .TP .I /etc/cron.d/ System crontab directory .SH SEE ALSO bcron\-sched(8), bcron\-spool(8), bcrontab(1) .SH EXTENSIONS When specifying day of week, both day 0 and day 7 will be considered Sunday. BSD and ATT seem to disagree about this. .PP Lists and ranges are allowed to co-exist in the same field. "1-3,7-9" would be rejected by ATT or BSD cron -- they want to see "1-3" or "7,8,9" ONLY. .PP Ranges can include "steps", so "1-9/2" is the same as "1,3,5,7,9". .PP Names of months or days of the week can be specified by name. .PP Environment variables can be set in the crontab. In BSD or ATT, the environment handed to child processes is basically the one from /etc/rc. .PP Command output is mailed to the crontab owner (BSD can't do this), can be mailed to a person other than the crontab owner (SysV can't do this), or the feature can be turned off and no mail will be sent at all (SysV can't do this either). .SH AUTHOR .nf Paul Vixie Charles Cazabon Bruce Guenter bcron-0.09/crontab.c0000664000076400007640000000552510251704762013721 0ustar bruceguenter#include #include #include #include #include #include #include #include #include #include #include "bcron.h" static int parse_assign(str* line, str* env) { unsigned i; unsigned nameend; for (i = 0; i < line->len; ++i) if (isspace(line->s[i]) || line->s[i] == '=') break; if (i >= line->len) return 0; nameend = i; while (i < line->len && isspace(line->s[i])) ++i; if (i >= line->len || line->s[i] != '=') return 0; ++i; while (i < line->len && isspace(line->s[i])) ++i; if (i >= line->len) return 0; if ((line->s[i] == '"' || line->s[i] == '\'') && line->s[line->len - 1] == line->s[i]) { ++i; line->s[line->len - 1] = 0; } line->s[nameend] = 0; wrap_str(envstr_set(env, line->s, line->s + i, 1)); return 1; } static struct job* parse_job(str* line, const char* runas, const str* environ) { struct job_timespec times; int i; char* s; s = line->s; if ((i = timespec_parse(×, s)) == 0 || !isspace(s[i])) return 0; for (s += i; isspace(*s); ++s) ; if (!runas) { runas = s; while (*s != 0 && !isspace(*s)) ++s; if (s == runas) return 0; *s++ = 0; while (*s != 0 && isspace(*s)) ++s; if (getpwnam(runas) == 0) { warn3("Unknown user: '", runas, "'"); return 0; } } if (*s == 0) return 0; return job_new(×, runas, s, environ); } int crontab_parse(struct crontab* c, str* data, const char* runas) { static str line; static str env; struct job* job; struct job* head = 0; struct job* tail = 0; striter iter; env.len = 0; striter_loop(&iter, data, '\n') { wrap_str(str_copyb(&line, iter.startptr, iter.len)); str_strip(&line); if (line.len == 0 || line.s[0] == '#') continue; if (parse_assign(&line, &env)) continue; if ((job = parse_job(&line, runas, &env)) != 0) { job->next = 0; job->nexttime = 0; if (head == 0) head = job; else tail->next = job; tail = job; } else warn3("Ignoring unparseable line: '", line.s, "'"); } c->jobs = head; return 1; } int crontab_import(struct crontab* c, const char* path, const char* runas) { static str file; struct ibuf in; if (c->jobs) { job_free(c->jobs); c->jobs = 0; } if (!ibuf_open(&in, path, 0)) { warn3sys("Could not open '", path, "'"); return 0; } minifstat(in.io.fd, &c->stat); file.len = 0; if (!ibuf_readall(&in, &file)) { warn3sys("Could not read '", path, "'"); ibuf_close(&in); return 0; } ibuf_close(&in); if (!crontab_parse(c, &file, runas)) { warn3("Could not parse '", path, "'"); return 0; } return 1; } void crontab_free(struct crontab* c) { job_free(c->jobs); free(c); } bcron-0.09/bcron-spool.80000664000076400007640000000120010251704762014435 0ustar bruceguenter.TH bcron-spool 8 .SH NAME bcron-spool \- Manage user crontab submissions .SH SYNOPSIS .B envuidgid $BCRON_USER unixserver -U /var/run/bcron-spool bcron-spool .SH DESCRIPTION .B bcron-spool manipulates the bcron spool on behalf of users. It accepts one of three commands: List an existing file, remove an existing file, copy given data into a new file. The name of the file in the spool directory is restricted to the username of the connecting process. .SH ENVIRONMENT .TP 5 .B BCRON_SPOOL The spool directory for bcron. Defaults to .IR /var/spool/cron . .SH SEE ALSO bcron-sched(8), bcrontab(1) .SH AUTHOR Bruce Guenter bcron-0.09/bcron-spool.c0000664000076400007640000001155110251704762014522 0ustar bruceguenter#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bcron.h" const char program[] = "bcron-spool"; const int msg_show_pid = 1; static str filename; static const char* username; static char** fixup_argv; static void respond(const char* msg) { obuf_putnetstring(&outbuf, msg, strlen(msg)); obuf_flush(&outbuf); switch (msg[0]) { case 'K': exit(0); case 'Z': die3sys(111, username, ": ", msg + 1); default: die3(100, username, ": ", msg + 1); } } static void respond_okstr(const str* s) { obuf_putu(&outbuf, s->len + 1); obuf_putc(&outbuf, ':'); obuf_putc(&outbuf, 'K'); obuf_putstr(&outbuf, s); obuf_putc(&outbuf, ','); obuf_flush(&outbuf); exit(0); } static void make_filename(const char* name) { if (!str_copy2s(&filename, CRONTAB_DIR "/", name)) respond("ZCould not produce filename"); } static int fixup(int fd) { int pid; int status; int newfd; if (fixup_argv != 0) { if (lseek(fd, 0, SEEK_SET) != 0) respond("ZCould not seek in temporary file"); if ((newfd = tempfile("tmp/spool")) == -1) respond("ZCould not create temporary file"); if ((pid = fork()) == -1) respond("ZCould not fork fixup program"); if (pid == 0) { dup2(fd, 0); close(fd); dup2(newfd, 1); close(newfd); execvp(fixup_argv[0], fixup_argv); die3sys(111, "Could not exec '", fixup_argv[0], "'"); } if (waitpid(pid, &status, 0) != pid) respond("ZWaitpid failed"); if (status != 0) respond("ZFilter failed"); fd = newfd; } return fd; } static void cmd_store(str* data) { int i; int fd; if ((i = str_findfirst(data, 0)) <= 0) respond("DStore command is missing data"); ++i; if ((fd = tempfile("tmp/spool")) == -1) respond("ZCould not create temporary file"); if (write(fd, data->s + i, data->len - i) != (long)(data->len - i) || (fd = fixup(fd)) == -1 || fchmod(fd, 0400) == -1 || close(fd) != 0) respond("ZCould not write temporary file"); if (rename(tempname.s, filename.s) != 0) respond("ZCould not rename temporary file"); trigger_pull(TRIGGER); respond("KCrontab successfully written"); } static void cmd_list(void) { str data = {0,0,0}; if (ibuf_openreadclose(filename.s, &data) == -1) { if (errno == ENOENT) respond("DCrontab does not exist"); else respond("ZCould not read crontab"); } respond_okstr(&data); } static void cmd_remove(void) { if (unlink(filename.s) != 0 && errno != ENOENT) respond("ZCould not remove crontab"); trigger_pull(TRIGGER); respond("KCrontab removed"); } static void cmd_listsys(void) { DIR* dir; direntry* entry; str data = {0,0,0}; if ((dir = opendir(CRONTAB_DIR)) == 0) respond("ZCould not open crontabs directory"); while ((entry = readdir(dir)) != 0) { if (entry->d_name[0] != ':') continue; make_filename(entry->d_name); if (!str_cat3s(&data, "==> ", entry->d_name, " <==\n")) respond("ZOut of memory"); if (ibuf_openreadclose(filename.s, &data) == -1) respond("ZCould not read crontab"); if (!str_catc(&data, '\n')) respond("ZOut of memory"); } closedir(dir); respond_okstr(&data); } static void logcmd(char cmd) { char cmdstr[3]; cmdstr[0] = cmd; cmdstr[1] = ' '; cmdstr[2] = 0; msg2(cmdstr, username); } int main(int argc, char* argv[]) { str packet = {0,0,0}; const char* s; uid_t euid = -1; const struct passwd* pw; if (chdir_bcron() != 0) respond("ZCould not change directory"); if (argc > 1) fixup_argv = argv + 1; if ((s = ucspi_protocol()) == 0 || (strcmp(s, "UNIX") != 0 && strcmp(s, "LOCAL") != 0) || (s = ucspi_getenv("REMOTEEUID")) == 0 || (euid = strtoul(s, (char**)&s, 0)) == (unsigned)-1 || *s != 0) respond("DConfiguration error: must be run from unixserver"); if (!ibuf_getnetstring(&inbuf, &packet) || packet.len < 2) respond("ZInvalid input data or read error"); /* Look up and validate username */ username = packet.s + 1; if ((pw = getpwnam(username)) == 0) respond("DInvalid or unknown username"); if (euid != 0 && euid != pw->pw_uid) respond("DUsername does not match invoking UID"); if (!str_copy2s(&filename, CRONTAB_DIR "/", pw->pw_name)) respond("ZCould not produce filename"); logcmd(packet.s[0]); /* Execute the command. */ switch (packet.s[0]) { case 'S': cmd_store(&packet); break; case 'L': cmd_list(); break; case 'R': cmd_remove(); break; case 'Y': if (euid != 0 && euid != getuid()) respond("DOnly root or cron can list system crontabs"); cmd_listsys(); break; } respond("DInvalid command code"); return 0; } bcron-0.09/bcron-start.80000664000076400007640000000072310251704762014447 0ustar bruceguenter.TH bcron-start 8 .SH NAME bcron-start \- Start the bcron scheduling system .SH SYNOPSIS .B bcron-start [ .I exec-wrapper ] .SH DESCRIPTION .B bcron-start .P .B bcron-exec should normally be run by .B bcron-start in conjunction with .BR bcron-sched . .SH ENVIRONMENT .TP 5 .B BCRON_USER .B bcron-start drops root privileges and switches to this user before starting .BR bcron-sched . .SH SEE ALSO bcron-sched(8), bcron-exec(8) .SH AUTHOR Bruce Guenter bcron-0.09/bcron-start.c0000664000076400007640000000351610251704762014525 0ustar bruceguenter#include #include #include #include #include #include #include #include "bcron.h" const char program[] = "bcron-start"; const int msg_show_pid = 0; int main(int argc, char* argv[]) { str path = {0,0,0}; int pipe1[2]; /* From -sched to -exec */ #ifdef IGNORE_EXEC int devnull; #else int pipe2[2]; /* From -exec to -sched */ #endif struct passwd* pw; int slash; const char* user; if ((devnull = open("/dev/null", O_RDWR, 0)) == -1) die1sys(111, "Could not open '/dev/null' for writing"); wrap_str(str_copys(&path, argv[0])); if ((slash = str_findlast(&path, '/')) < 0) path.len = slash = 0; else path.len = ++slash; if ((user = getenv("BCRON_USER")) == 0) user = BCRON_USER; if ((pw = getpwnam(user)) == 0) die1(111, "Could not look up cron user"); if (pipe(pipe1) != 0) die1sys(111, "Could not create pipe"); wrap_str(str_cats(&path, "bcron-exec")); switch (fork()) { case -1: die1sys(111, "Could not fork"); case 0: dup2(pipe1[0], 0); close(pipe1[0]); close(pipe1[1]); #ifdef IGNORE_EXEC dup2(devnull, 1); close(devnull); #else dup2(pipe2[1], 1); close(pipe2[0]); close(pipe2[1]); #endif argv[0] = path.s; execvp(argv[0], argv); die1sys(111, "Could not exec bcron-exec"); } if (setgid(pw->pw_gid) != 0 || setuid(pw->pw_uid) != 0) die1sys(111, "Could not setuid"); path.len = slash; wrap_str(str_cats(&path, "bcron-sched")); argv[0] = path.s; argv[1] = 0; dup2(pipe1[1], 1); close(pipe1[0]); close(pipe1[1]); #ifdef IGNORE_EXEC dup2(devnull, 0); close(devnull); #else dup2(pipe2[0], 0); close(pipe2[0]); close(pipe2[1]); #endif execvp(argv[0], argv); die1sys(111, "Could not exec bcron-sched"); return 111; (void)argc; } bcron-0.09/insthier.c0000664000076400007640000000060210251704762014105 0ustar bruceguenter#include #include "conf_bin.c" void insthier(void) { int dir = opendir(conf_bin); c(dir, "bcron-exec", -1, -1, 0755); c(dir, "bcron-sched", -1, -1, 0755); c(dir, "bcron-spool", -1, -1, 0755); c(dir, "bcron-start", -1, -1, 0755); c(dir, "bcron-update", -1, -1, 0755); c(dir, "bcrontab", -1, -1, 0755); s(dir, "crontab", "bcrontab"); } bcron-0.09/conf-bin0000664000076400007640000000007610251704762013537 0ustar bruceguenter/usr/local/bin Programs will be installed in this directory. bcron-0.09/bcron-update.80000664000076400007640000000257310251704762014601 0ustar bruceguenter.TH bcron-update 8 .SH NAME bcron-update \- Update system crontabs. .SH SYNOPSIS .B bcron-update .I path [ .I path ... ] .SH DESCRIPTION .B bcron-update polls the named files or directories periodically to see if there are any new, changed, or removed files. When it detects changes, it mirrors those changes into the crontab spool directory. .B bcron-update runs as root in order to be able to read system files that would potentially be unreadable otherwise. .SH EXAMPLES To mirror modern vixie-cron's behavior, use: .EX bcron-update /etc/crontab /etc/cron.d .EE .SH ENVIRONMENT .TP 5 .B BCRON_SPOOL The spool directory for bcron. Defaults to .IR /var/spool/cron . .TP 5 .B BCRON_USER After writing files and before moving them into their final location, .B bcron-update changes the ownership of the file to this user so that .B bcron-sched can read them. .SH SEE ALSO bcron-sched(8) .SH DIAGNOSTICS .B bcron-update outputs three different kinds of messages about actions it is taking. .TP 5 .I Rescanning /etc/cron.d The named directory has been modified, and will be scanned to determine what files have been added or deleted. .TP 5 .I -/etc/cron.d/oldfile The named file no longer exists and will be removed from the spool. .TP 5 .I +/etc/cron.d/newfile The named file was either created or modified since the last scan, and will be copied into the spool. .SH AUTHOR Bruce Guenter bcron-0.09/bcron-update.c0000664000076400007640000001527610251704762014660 0ustar bruceguenter#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bcron.h" const char program[] = "bcron-update"; const int msg_show_pid = 0; static const struct passwd* cronpw; /*****************************************************************************/ GHASH_DECL(statcache,const char*,struct ministat); GHASH_DEFN(statcache,const char*,struct ministat, adt_hashsp,adt_cmpsp,adt_copysp,0,adt_freesp,0); /*****************************************************************************/ static void copy(const char* pathin, const char* pathout) { int fdin; int fdout; char buf[4096]; long rd; if ((fdin = open(pathin, O_RDONLY)) == -1) { if (errno == ENOENT) /* File disappeared, ignore */ return; die3sys(111, "Could not open '", pathin, "'"); } if ((fdout = tempfile("tmp/")) == -1) die1sys(111, "Could not create temporary file"); while ((rd = read(fdin, buf, sizeof buf)) > 0) { if (write(fdout, buf, rd) != rd) die3sys(111, "Could not write '", tempname.s, "'"); } if (rd < 0) die3sys(111, "Could not read '", pathin, "'"); close(fdin); if (fchown(fdout, cronpw->pw_uid, cronpw->pw_gid) != 0) die3sys(111, "Could not change ownership on '", tempname.s, "'"); if (fchmod(fdout, 0400) != 0) die3sys(111, "Could not change permissions on '", tempname.s, "'"); if (close(fdout) != 0) die3sys(111, "Could not write '", tempname.s, "'"); if (rename(tempname.s, pathout) != 0) die5sys(111, "Could not rename '", tempname.s, "' to '", pathout, "'"); tempname.len = 0; } static str path; static str dstpath; static str tmp; static int actions; static void act(char a, const char* s1, const char* s2) { wrap_str(str_copys(&path, s1)); if (s2 != 0) { wrap_str(str_catc(&path, '/')); wrap_str(str_cats(&path, s2)); } wrap_str(str_copy(&tmp, &path)); str_subst(&tmp, '/', ':'); wrap_str(str_copys(&dstpath, CRONTAB_DIR "/")); wrap_str(str_cat(&dstpath, &tmp)); switch (a) { case '-': msg2("-", path.s); unlink(dstpath.s); break; default: msg2("+", path.s); copy(path.s, dstpath.s); } actions = 1; } /*****************************************************************************/ struct arg { const char* path; DIR* dir; struct ghash entries; struct ministat st; }; static struct arg* args; /*****************************************************************************/ static int check_file(struct arg* a, struct ministat* st) { if (!st->exists) { if (a->st.exists) { act('-', a->path, 0); a->st.exists = 0; return 1; } } else { if (memcmp(&a->st, st, sizeof *st) != 0) { act(a->st.exists ? '*' : '+', a->path, 0); a->st = *st; return 1; } } return 0; } static int check_dir(struct arg* a, struct ministat* st) { int count; struct ghashiter i; direntry* e; count = 0; /* Only need to scan for new files if the directory has changed. */ if (memcmp(&a->st, st, sizeof *st) != 0) { memcpy(&a->st, st, sizeof *st); msg2("Rescanning ", a->path); rewinddir(a->dir); while ((e = readdir(a->dir)) != 0) { struct statcache_entry* se; const char* name = e->d_name; if (name[0] == '.') continue; if ((se = statcache_get(&a->entries, &name)) == 0) { /* File is new. */ ministat2(a->path, name, st); if (S_ISREG(st->mode)) { act('+', a->path, name); if (!statcache_add(&a->entries, &name, st)) die_oom(111); } } } } ghashiter_loop(&i, &a->entries) { struct statcache_entry* se = i.entry; ministat2(a->path, se->key, st); if (st->exists) { if (memcmp(&se->data, st, sizeof *st) != 0 && S_ISREG(st->mode)) { memcpy(&se->data, st, sizeof *st); act('*', a->path, se->key); } } else { act('-', a->path, se->key); statcache_remove(&a->entries, &se->key); } } return count; } static void checkdirs(int argc) { int i; struct arg* a; struct ministat st; actions = 0; for (i = 0, a = args; i < argc; ++i, ++a) { ministat(a->path, &st); if (a->dir) check_dir(a, &st); else check_file(a, &st); } if (actions) trigger_pull(TRIGGER); } static void checkpaths(int argc, char* argv[]) { int i; int j; int k; for (i = 0; i < argc; ++i) { char* arg = argv[i]; /* Make sure all filenames are absolute. */ if (arg[0] != '/') die1(111, "All filenames must be absolute"); /* Make sure there are no doubled slashes. */ for (j = k = 1; arg[j] != 0; ++j) { if (arg[j] != '/' || arg[j-1] != '/') arg[k++] = arg[j]; } /* Strip off any trailing slashes. */ if (arg[k-1] == '/') --k; arg[k] = 0; } } static void opendirs(int argc, char* argv[]) { int i; struct ministat st; /* Setup the argument array. */ if ((args = malloc(argc * sizeof *args)) == 0) die_oom(111); memset(args, 0, argc * sizeof *args); for (i = 0; i < argc; ++i) { args[i].path = argv[i]; ministat(argv[i], &st); if (!st.exists) die3(111, "Path does not exist: '", argv[i], "'"); if (S_ISDIR(st.mode)) { if ((args[i].dir = opendir(argv[i])) == 0) die3sys(111, "Could not open directory '", argv[i], "'"); statcache_init(&args[i].entries); } else args[i].dir = 0; } } /* Purge all the entries in the crontabs directory generated by this * program. */ static void purge(int argc, char* argv[]) { DIR* dir; direntry* e; int i; if ((dir = opendir(CRONTAB_DIR)) == 0) die1sys(111, "Could not open directory '" CRONTAB_DIR "'"); for (i = 0; i < argc; ++i) { wrap_str(str_copys(&tmp, argv[i])); str_subst(&tmp, '/', ':'); rewinddir(dir); while ((e = readdir(dir)) != 0) { if (memcmp(e->d_name, tmp.s, tmp.len) == 0 && (e->d_name[tmp.len] == 0 || e->d_name[tmp.len] == ':')) { wrap_str(str_copy2s(&path, CRONTAB_DIR "/", e->d_name)); unlink(path.s); } } } closedir(dir); } int main(int argc, char* argv[]) { const char* user; if ((user = getenv("BCRON_USER")) == 0) user = BCRON_USER; if ((cronpw = getpwnam(user)) == 0) die1(111, "Could not look up cron user"); /* Make sure there are files to check. */ if (argc < 2) die1(111, "Must give at least one filename or directory on the command-line"); checkpaths(argc - 1, argv + 1); opendirs(argc - 1, argv + 1); if (chdir_bcron() != 0) die1sys(111, "Could not change directory"); purge(argc - 1, argv + 1); for(;;) { checkdirs(argc - 1); iopoll(0, 0, 60 * 1000); } } bcron-0.09/VERSION0000664000076400007640000000001310251704762013160 0ustar bruceguenterbcron 0.09 bcron-0.09/timespec-parse.c0000664000076400007640000000605410251704762015210 0ustar bruceguenter#include #include #include #include "bcron.h" static int parse_value(const char* s, int* out, const char** table) { int v; int i; if (!isdigit(*s)) { if (table != 0) { for (i = 0; table[i] != 0; ++i) { int len; if ((len = strlen(table[i])) == 0) continue; if (strncasecmp(table[i], s, len) == 0 && !isalpha(s[len])) { *out = i; return len; } if (strncasecmp(table[i], s, 3) == 0 && !isalpha(s[3])) { *out = i; return 3; } } } return 0; } for (i = v = 0; isdigit(s[i]); ++i) v = (v * 10) + (s[i] - '0'); *out = v; return i; } static int parse_field(const char* s, bitmap* out, int min, int max, bitmap all, const char** table, int numoffset) { int start; int i; int startbit; int endbit; int bit; int step; bitmap bits; if (s[0] == '*' && isspace(s[1])) { *out = all; return 1; } bits = i = 0; do { start = i; step = 1; if (s[i] == '*') { ++i; startbit = min; endbit = max; } else { if ((i += parse_value(s+i, &startbit, table)) == start) return 0; startbit -= numoffset; endbit = startbit; if (s[i] == '-') { ++i; start = i; if ((i += parse_value(s+i, &endbit, table)) == start) return 0; endbit -= numoffset; } } if (s[i] == '/') { ++i; start = i; if ((i += parse_value(s+i, &step, 0)) == start) return 0; } if (startbit > endbit || startbit < min || endbit > max) return 0; bit = startbit; do { bits |= 1ULL << bit; bit += step; } while (bit <= endbit); while (s[i] == ',') ++i; } while (!isspace(s[i])); *out = bits; return i; } static const char* wday_table[] = { "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", 0 }; static const char* month_table[] = { "", "january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december", 0 }; int timespec_parse(struct job_timespec* t, const char* s) { int i; bitmap b; const char* start; start = s; if ((i = parse_field(s, &t->minutes, 0, 59, ALL_MINUTES, 0, 0)) <= 0 || !isspace(s[i])) return 0; for (s += i; isspace(*s); ++s) ; if ((i = parse_field(s, &t->hours, 0, 23, ALL_HOURS, 0, 0)) <= 0 || !isspace(s[i])) return 0; for (s += i; isspace(*s); ++s) ; if ((i = parse_field(s, &t->mdays, 1, 31, ALL_MDAYS, 0, 0)) <= 0 || !isspace(s[i])) return 0; for (s += i; isspace(*s); ++s) ; if ((i = parse_field(s, &t->months, 0, 11, ALL_MONTHS, month_table, 1)) <= 0 || !isspace(s[i])) return 0; for (s += i; isspace(*s); ++s) ; if ((i = parse_field(s, &t->wdays, 0, 7, ALL_WDAYS, wday_table, 0)) <= 0 || !isspace(s[i])) return 0; s += i; if (t->wdays & (1 << 7)) { t->wdays |= 1; t->wdays &= ALL_WDAYS; } for (b = t->hours, i = 0; b != 0; b >>= 1) i += (unsigned)b & 1; t->hour_count = i; return s - start; } bcron-0.09/time.c0000664000076400007640000000323210251704762013220 0ustar bruceguenter#include #include #include "bcron.h" const char* fmttm(const struct tm* tm) { static char buf[30]; buf[strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S %Z", tm)] = 0; return buf; } const char* fmttime(time_t t) { return fmttm(localtime(&t)); } #if 0 static time_t mktime_internal(int year, int month, int mday, int hour, int minute, int dst) { struct tm tm; if (year > 1900) year -= 1900; printf("mktime_internal(%d,%d,%d,%d,%d,%d)\n", year,month,mday,hour,minute,dst); memset(&tm, 0, sizeof tm); tm.tm_min = minute; tm.tm_hour = hour; tm.tm_mday = mday; tm.tm_mon = month; tm.tm_year = year; tm.tm_isdst = dst; /* Another stupid: mktime can alter the contents of its parameter, so none of the contents can be trusted to have retained their value. */ return mktime(&tm); } time_t mktimesep(int year, int month, int mday, int hour, int minute, int* dst) { time_t t; const struct tm* lt; /* Try with DST set first: If the time zone doesn't do DST changes, the DST flag is ignored. If the time zone does do DST changes, this will cause mktime to select the first part of the skipped hour. Did I ever mention how much I hate DST? */ t = mktime_internal(year, month, mday, hour, minute, *dst); /* Stupid mktime interface requires IN ADVANCE knowledge of the state of DST, which is almost impossible without doing the reverse conversion (localtime) first. */ lt = localtime(&t); if (hour != lt->tm_hour || *dst != lt->tm_isdst) { printf("mktimesep retry\n"); *dst = !*dst; t = mktime_internal(year, month, mday, hour, minute, *dst); } return t; } #endif bcron-0.09/ANNOUNCEMENT0000664000076400007640000000600610251704762013735 0ustar bruceguenterVersion 0.09 of bcron is now available at: http://untroubled.org/bcron/ ------------------------------------------------------------------------------ Changes in version 0.09 - Fixed several build and portability issues. - The default path for bcron-spool is changed to /var/run/bcron-spool Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- bcron Short Description Bruce Guenter Version 0.09 2005-06-08 This is bcron, a new cron system designed with secure operations in mind. To do this, the system is divided into several seperate programs, each responsible for a seperate task, with strictly controlled communications between them. The user interface is a drop-in replacement for similar systems (such as vixie-cron), but the internals differ greatly. A mailing list has been set up to discuss this and other packages. To subscribe, send an email to: bgware-subscribe@lists.untroubled.org A mailing list archive is available at: http://lists.untroubled.org/?list=bgware Development versions of bcron are available via Subversion at: svn://bruce-guenter.dyndns.org/bcron/trunk Requirements: - Needs ucspi-unix (or ucspi-local) to be able to accept user-submitted crontabs. - bglibs version 1.021 or later from http://untroubled.org/bglibs/ Installation: - Build the sources by running "make" - Run the "instshow" command to see what will be installed (optional). - After the package has been compiled, run the following command as root: make install This command will produce no output if there are no errors. Configuration: - Create a system user and group "cron". This user will own all the crontab files. - Create the spool directory as follows: mkdir -p /var/spool/cron/crontabs /var/spool/cron/tmp mkfifo /var/spool/cron/trigger for i in crontabs tmp trigger; do chown cron:cron /var/spool/cron/$i chmod go-rwx /var/spool/cron/$i done - Create the configuration directory /etc/bcron as follows: mkdir -p /etc/bcron You can put any common configuration settings into this directory, like alternate spool directories in BCRON_SPOOL. Operation: - The scheduler can be started with the following command: envdir /etc/bcron bcron-start | multilog t /var/log/bcron & - To accept crontabs from users, run the spooler: envdir /etc/bcron \ envuidgid cron \ unixserver -U /var/run/bcron-spool \ bcron-spool >/dev/null 2>&1 & - To update system crontabs, run the updater: envdir /etc/bcron \ bcron-update /etc/crontab /etc/cron.d >/dev/null 2>&1 & - Sample run scripts are provided for operating the above commands under svscan and supervise (or equivalents such as runit). This package is Copyright(C) 2005 Bruce Guenter or FutureQuest, Inc., and may be copied according to the GNU GENERAL PUBLIC LICENSE (GPL) Version 2 or a later version. A copy of this license is included with this package. This package comes with no warranty of any kind. bcron-0.09/bcron.h0000664000076400007640000000626010251704762013376 0ustar bruceguenter#ifndef BCRON__H__ #define BCRON__H__ #include #include #include #include #define BCRON_SPOOL "/var/spool/cron" #define CRONTAB_DIR "crontabs" #define SOCKET_PATH "/var/run/bcron-spool" #define BCRON_USER "cron" #define TRIGGER "trigger" #define DEBUG_MISC 1 #define DEBUG_SCHED 2 #define DEBUG_JOBS 4 #define DEBUG_CONN 8 #define IGNORE_EXEC 1 /*****************************************************************************/ extern str tempname; int tempfile(const char* prefix); /*****************************************************************************/ const char* fmttm(const struct tm*); const char* fmttime(time_t t); /*****************************************************************************/ struct ministat { int exists; dev_t device; ino_t inode; mode_t mode; size_t size; time_t mtime; }; void ministat(const char* path, struct ministat* s); void ministat2(const char* base, const char* entry, struct ministat* s); void minifstat(int fd, struct ministat* s); /*****************************************************************************/ struct connection { int fd; enum { LENGTH, DATA, COMMA } state; unsigned long length; str packet; void* data; }; void connection_init(struct connection* c, int fd, void* data); int connection_read(struct connection* c, void (*handler)(struct connection*)); /*****************************************************************************/ int sendpacket(int fd, const str* s); /*****************************************************************************/ int chdir_bcron(void); /*****************************************************************************/ typedef uint64 bitmap; struct job_timespec { bitmap minutes; /* bitmap: 0-59 */ bitmap hours; /* bitmap: 0-23 */ bitmap mdays; /* bitmap: 1-31 */ bitmap months; /* bitmap: 0-11 */ bitmap wdays; /* bitmap: 0-6 */ int hour_count; }; void timespec_next_init(void); time_t timespec_next(const struct job_timespec* ts, time_t start, const struct tm* starttm); int timespec_parse(struct job_timespec* t, const char* s); /*****************************************************************************/ struct job { struct job_timespec times; time_t nexttime; const char* runas; const char* command; str environ; struct job* next; }; #define ALL_MINUTES 0xfffffffffffffffULL #define ALL_HOURS 0xffffffUL #define ALL_MDAYS 0xfffffffeUL #define ALL_MONTHS 0xfffUL #define ALL_WDAYS 0x7fUL struct job* job_new(const struct job_timespec* times, const char* runas, const char* command, const str* environ); void job_free(struct job* job); void job_exec(struct job* job); /*****************************************************************************/ struct crontab { struct ministat stat; struct job* jobs; }; int crontab_parse(struct crontab* c, str* data, const char* runas); int crontab_import(struct crontab* c, const char* path, const char* runas); void crontab_free(struct crontab* c); /*****************************************************************************/ extern struct ghash crontabs; GHASH_DECL(crontabs,const char*,struct crontab); void crontabs_load(void); #endif bcron-0.09/conf-bgincs0000664000076400007640000000003210251704762014224 0ustar bruceguenter/usr/local/bglibs/include bcron-0.09/conf-bglibs0000664000076400007640000000002610251704762014224 0ustar bruceguenter/usr/local/bglibs/lib bcron-0.09/bcron-update.run0000664000076400007640000000011410251704762015223 0ustar bruceguenter#!/bin/sh exec >/dev/null 2>&1 exec \ bcron-update /etc/crontab /etc/cron.d bcron-0.09/bcron-exec.80000664000076400007640000000106610251704762014237 0ustar bruceguenter.TH bcron-exec 8 .SH NAME bcron-exec \- Execute jobs on behalf of .B bcron-sched .SH SYNOPSIS .B bcron-exec [ .I wrapper ] .SH DESCRIPTION .B bcron-exec reads formatted program execution instructions from standard input and executes the appropriate program. If the execution of the program produces any output, an email is produced and sent to the recipient given in the instructions. .P .B bcron-exec should normally be run by .B bcron-start in conjunction with .BR bcron-sched . .SH SEE ALSO bcron-sched(8), bcron-start(8) .SH AUTHOR Bruce Guenter bcron-0.09/bcron-exec.c0000664000076400007640000002212010251704762014304 0ustar bruceguenter#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bcron.h" const char program[] = "bcron-exec"; const int msg_show_pid = 0; extern char** environ; static const char** shell_argv; static int shell_argc; #define SLOT_MAX 512 struct slot { pid_t pid; int tmpfd; long headerlen; int sending_email; str id; struct passwd pw; }; static struct slot slots[SLOT_MAX]; static int slots_used = 0; static const char* sendmail[7] = { "/usr/sbin/sendmail", "-FCronDaemon", "-i", "-odi", "-oem", "-t", 0 }; /* Input: ID NUL username NUL command NUL environment NUL NUL Output: ID NUL code message NUL Codes: K Job completed Z Temporary error running job D Invalid job specification */ static const char* path; static str tmp; static const char* tmpprefix = "/tmp/bcron"; #define NUL 0 static int devnull; static void report(const char* id, const char* msg) { wrap_str(str_copyb(&tmp, id, strlen(id) + 1)); wrap_str(str_cats(&tmp, msg)); if (!sendpacket(1, &tmp)) exit(111); } static void failsys(const char* id, const char* msg) { wrap_str(str_copyb(&tmp, id, strlen(id) + 1)); wrap_str(str_cats(&tmp, msg)); wrap_str(str_cats(&tmp, ": ")); wrap_str(str_cats(&tmp, strerror(errno))); if (!sendpacket(1, &tmp)) exit(111); } static void init_slots(void) { int slot; for (slot = 0; slot < SLOT_MAX; ++slot) { slots[slot].pid = 0; slots[slot].tmpfd = -1; wrap_str(str_ready(&slots[slot].id, 16)); } } static int pick_slot(void) { int slot; for (slot = 0; slot < SLOT_MAX; ++slot) if (slots[slot].pid == 0) return slot; return -1; } static void exec_cmd(int fdin, int fdout, const char** argv, const str* env, const struct passwd* pw) { dup2(fdin, 0); close(fdin); dup2(fdout, 1); dup2(fdout, 2); close(fdout); if (0 && initgroups(pw->pw_name, pw->pw_gid) != 0) die1sys(111, "Could not initgroups"); if (setgid(pw->pw_gid) != 0) die1sys(111, "Could not setgid"); if (setuid(pw->pw_uid) != 0) die1sys(111, "Could not setuid"); if (chdir(pw->pw_dir) != 0) die1sys(111, "Could not change directory"); if (env) if ((environ = envstr_make_array(env)) == 0) die_oom(111); execv(argv[0], (char**)argv); die3sys(111, "Could not execute '", argv[0], "'"); exit(111); } static int forkexec_slot(int slot, int fdin, int fdout, const char** argv, const str* env) { pid_t pid; const struct passwd* pw = &slots[slot].pw; switch (pid = fork()) { case -1: failsys(slots[slot].id.s, "ZFork failed"); return 0; case 0: exec_cmd(fdin, fdout, argv, env, pw); } slots[slot].pid = pid; ++slots_used; return 1; } static int strcopy(char** dest, const char* src) { if (*dest) free(*dest); return (*dest = strdup(src)) != 0; } static int init_slot(int slot, const struct passwd* pw) { struct slot* s; s = &slots[slot]; if (!strcopy(&s->pw.pw_name, pw->pw_name)) return 0; s->pw.pw_passwd = 0; s->pw.pw_uid = pw->pw_uid; s->pw.pw_gid = pw->pw_gid; s->pw.pw_gecos = 0; if (!strcopy(&s->pw.pw_dir, pw->pw_dir)) return 0; s->pw.pw_shell = 0; return 1; } static void start_slot(int slot, const char* command, const char* envstart) { static str env; char* period; int fd; char hostname[256]; const char* mailto; const struct passwd* pw = &slots[slot].pw; const char* shell; msg5("(", pw->pw_name, ") CMD (", command, ")"); env.len = 0; wrap_str(envstr_set(&env, "PATH", path, 1)); if (envstart) wrap_str(envstr_from_string(&env, envstart, 1)); wrap_str(envstr_set(&env, "HOME", pw->pw_dir, 1)); wrap_str(envstr_set(&env, "USER", pw->pw_name, 1)); wrap_str(envstr_set(&env, "LOGNAME", pw->pw_name, 1)); if ((shell = envstr_get(&env, "SHELL")) == 0) shell = "/bin/sh"; if ((mailto = envstr_get(&env, "MAILTO")) == 0) mailto = pw->pw_name; if (*mailto == 0) { fd = devnull; slots[slot].headerlen = 0; } else { if ((fd = path_mktemp(tmpprefix, &tmp)) == -1) { failsys(slots[slot].id.s, "ZCould not create temporary file"); return; } unlink(tmp.s); gethostname(hostname, sizeof hostname); wrap_str(str_copyns(&tmp, 6, "To: <", mailto, ">\n", "From: Cron Daemon \n")); if ((period = strchr(hostname, '.')) != 0) *period = 0; wrap_str(str_catns(&tmp, 7, "Subject: Cron <", pw->pw_name, "@", hostname, "> ", command, "\n\n")); slots[slot].headerlen = tmp.len; if (write(fd, tmp.s, tmp.len) != (long)tmp.len) { close(fd); fd = -1; report(slots[slot].id.s, "ZCould not write message header"); return; } } shell_argv[shell_argc+0] = shell; shell_argv[shell_argc+1] = "-c"; shell_argv[shell_argc+2] = command; if (!forkexec_slot(slot, devnull, fd, shell_argv, &env)) { if (fd != devnull) close(fd); fd = -1; } slots[slot].sending_email = 0; slots[slot].tmpfd = fd; } static void end_slot(int slot, int status) { struct stat st; slots[slot].pid = 0; --slots_used; if (slots[slot].sending_email) { slots[slot].sending_email = 0; if (status) report(slots[slot].id.s, "ZJob complete, sending email failed"); else report(slots[slot].id.s, "KJob complete, email sent"); } else { /* No header, no possible way to send email. */ if (slots[slot].headerlen == 0) report(slots[slot].id.s, "KJob complete"); else { /* If the job crashed, make sure it is noted. */ if (WIFSIGNALED(status)) { wrap_str(str_copys(&tmp, "\n\nJob was killed by signal #")); wrap_str(str_cati(&tmp, WTERMSIG(status))); wrap_str(str_catc(&tmp, '\n')); write(slots[slot].tmpfd, tmp.s, tmp.len); } if (fstat(slots[slot].tmpfd, &st) == -1) failsys(slots[slot].id.s, "ZCould not fstat"); else if (st.st_size > slots[slot].headerlen) { if (lseek(slots[slot].tmpfd, SEEK_SET, 0) != 0) failsys(slots[slot].id.s, "ZCould not lseek"); else { forkexec_slot(slot, slots[slot].tmpfd, devnull, sendmail, 0); slots[slot].sending_email = 1; } } else report(slots[slot].id.s, "KJob complete"); } /* To simplify the procedure, close the temporary file early. * The email sender still has it open, and will effect the final * deletion of the file when it completes. */ close(slots[slot].tmpfd); slots[slot].tmpfd = -1; } } #define FAIL(S,M) do{ report(S,M); return; }while(0) static void handle_packet(struct connection* c) { str* packet; const char* id; const char* runas; const char* command; const char* envstart; long slot; unsigned int i; struct passwd* pw; packet = &c->packet; id = packet->s; if (*id == NUL) FAIL(id, "DInvalid ID"); if ((slot = pick_slot()) < 0) FAIL(id, "DCould not allocate a slot"); wrap_str(str_copys(&slots[slot].id, id)); if ((i = str_findfirst(packet, NUL) + 1) >= packet->len) FAIL(id, "DInvalid packet"); runas = packet->s + i; if (*runas == NUL || (pw = getpwnam(runas)) == 0) FAIL(id, "DInvalid username"); if ((i = str_findnext(packet, NUL, i) + 1) >= packet->len) FAIL(id, "DInvalid packet"); command = packet->s + i; if ((i = str_findnext(packet, NUL, i) + 1) >= packet->len) envstart = 0; else { envstart = packet->s + i; wrap_str(str_catc(packet, 0)); } if (!init_slot(slot, pw)) FAIL(id, "ZOut of memory"); start_slot(slot, command, envstart); } static void handle_child(int wnh) { pid_t pid; int slot; int status; while ((pid = waitpid(-1, &status, wnh)) != -1 && pid != 0) { for (slot = 0; slot < SLOT_MAX; ++slot) { if (slots[slot].pid == pid) { end_slot(slot, status); break; } } } } int main(int argc, char* argv[]) { struct connection conn; iopoll_fd fds[2]; int selfpipe; int i; if ((shell_argv = malloc((argc + 3) * sizeof *argv)) == 0) die_oom(111); for (i = 1; i < argc; ++i) shell_argv[i-1] = argv[i]; for (; i < argc + 4; ++i) shell_argv[i-1] = 0; shell_argc = argc - 1; if ((path = getenv("PATH")) == 0) die1(111, "No PATH is set"); if ((devnull = open("/dev/null", O_RDWR)) == -1) die1sys(111, "Could not open \"/dev/null\""); if (!nonblock_on(0)) die1sys(111, "Could not set non-blocking status"); if ((selfpipe = selfpipe_init()) == -1) die1sys(111, "Could not create self-pipe"); init_slots(); connection_init(&conn, 0, 0); fds[0].fd = 0; fds[0].events = IOPOLL_READ; fds[1].fd = selfpipe; fds[1].events = IOPOLL_READ; for (;;) { if (iopoll_restart(fds, 2, -1) == -1) die1sys(111, "Poll failed"); if (fds[0].revents) if (connection_read(&conn, handle_packet) <= 0) break; if (fds[1].revents) { read(selfpipe, &i, 1); handle_child(WNOHANG); } } msg1("Waiting for remaining slots to complete"); while (slots_used > 0) handle_child(0); return 0; } bcron-0.09/connection.c0000664000076400007640000000240210251704762014417 0ustar bruceguenter#include #include #include #include #include #include #include #include "bcron.h" void connection_init(struct connection* c, int fd, void* data) { memset(c, 0, sizeof *c); c->fd = fd; c->state = LENGTH; c->data = data; nonblock_on(fd); } int connection_read(struct connection* c, void (*handler)(struct connection*)) { static char buf[512]; long rd; long i; if ((rd = read(c->fd, buf, sizeof buf)) <= 0) return rd; for (i = 0; i < rd; ) { /* Optimize packet appends */ if (c->state == DATA) { long todo = rd - i; if (c->packet.len + todo >= c->length) { todo = c->length - c->packet.len; c->state = COMMA; } str_catb(&c->packet, buf + i, todo); i += todo; } else { const char ch = buf[i++]; switch (c->state) { case LENGTH: if (ch == ':') { if (!str_ready(&c->packet, c->length)) { errno = ENOMEM; return -1; } c->packet.len = 0; c->state = DATA; } else if (isdigit(ch)) c->length = c->length * 10 + ch - '0'; break; case COMMA: if (ch == ',') { c->state = LENGTH; handler(c); c->length = 0; } break; case DATA: ; } } } return rd; } bcron-0.09/tempfile.c0000664000076400007640000000051610251704762014071 0ustar bruceguenter#include #include #include #include "bcron.h" str tempname = {0,0,0}; static void cleanup(void) { if (tempname.len > 0) unlink(tempname.s); } int tempfile(const char* prefix) { if (tempname.s == 0) atexit(cleanup); else cleanup(); return path_mktemp(prefix, &tempname); } bcron-0.09/ChangeLog0000664000076400007640000003125110251704762013672 0ustar bruceguenter------------------------------------------------------------------------ r49 | bruce | 2005-06-08 18:10:12 -0600 (Wed, 08 Jun 2005) | 2 lines Changed paths: M /trunk/tests.inc The spool directory is specified by $BCRON_SPOOL not $BCRON_HOME. ------------------------------------------------------------------------ r48 | bruce | 2005-06-08 18:02:33 -0600 (Wed, 08 Jun 2005) | 2 lines Changed paths: M /trunk/NEWS M /trunk/README.in M /trunk/bcron-spool.8 M /trunk/bcron-spool.run M /trunk/bcron.h M /trunk/bcron.texi M /trunk/bcrontab.1 The default path for bcron-spool is changed to /var/run/bcron-spool ------------------------------------------------------------------------ r47 | bruce | 2005-06-08 17:19:09 -0600 (Wed, 08 Jun 2005) | 2 lines Changed paths: M /trunk/NEWS Added note about changes so far. ------------------------------------------------------------------------ r46 | bruce | 2005-06-08 13:36:07 -0600 (Wed, 08 Jun 2005) | 2 lines Changed paths: M /trunk/bcrontab.1 Fixed up some formatting typos. ------------------------------------------------------------------------ r45 | bruce | 2005-06-08 13:35:10 -0600 (Wed, 08 Jun 2005) | 2 lines Changed paths: M /trunk/README.in Document the bglibs version requirement. ------------------------------------------------------------------------ r44 | bruce | 2005-06-08 13:31:31 -0600 (Wed, 08 Jun 2005) | 2 lines Changed paths: M /trunk/spec Use the easier install mechanism that is part of bglibs-1.021 ------------------------------------------------------------------------ r43 | bruce | 2005-06-08 00:10:30 -0600 (Wed, 08 Jun 2005) | 3 lines Changed paths: M /trunk/timespec-next.c Remove the extern declaration of "daylight", as the next bglibs will contain a sysdep for it. ------------------------------------------------------------------------ r42 | bruce | 2005-04-25 15:24:48 -0600 (Mon, 25 Apr 2005) | 2 lines Changed paths: M /trunk/bcron-sched.8 Added a list of actual files used by bcron-sched to its manpage. ------------------------------------------------------------------------ r41 | bruce | 2005-04-25 15:23:56 -0600 (Mon, 25 Apr 2005) | 2 lines Changed paths: M /trunk/README.in Be more precise on which files need ownership and permissions changed. ------------------------------------------------------------------------ r40 | bruce | 2005-04-20 12:37:45 -0600 (Wed, 20 Apr 2005) | 2 lines Changed paths: M /trunk/crontab.5 Tidied up the columns in the FILES section. ------------------------------------------------------------------------ r39 | bruce | 2005-04-01 17:35:57 -0600 (Fri, 01 Apr 2005) | 2 lines Changed paths: M /trunk/timespec-next.c Fixed a missing #include ------------------------------------------------------------------------ r38 | bruce | 2005-04-01 17:35:26 -0600 (Fri, 01 Apr 2005) | 2 lines Changed paths: M /trunk/NEWS M /trunk/VERSION Bumped version to 0.09 ------------------------------------------------------------------------ r37 | bruce | 2005-04-01 16:09:16 -0600 (Fri, 01 Apr 2005) | 2 lines Changed paths: M /trunk/README.in Added note about runit. ------------------------------------------------------------------------ r36 | bruce | 2005-03-31 11:23:35 -0600 (Thu, 31 Mar 2005) | 2 lines Changed paths: M /trunk/crontab.5 Fixed up typo in comments in sample crontab. ------------------------------------------------------------------------ r35 | bruce | 2005-03-30 23:53:25 -0600 (Wed, 30 Mar 2005) | 2 lines Changed paths: M /trunk/bcron.texi Fixed a typo: "jobs line" should have been "jobs like" ------------------------------------------------------------------------ r33 | bruce | 2005-03-30 23:45:46 -0600 (Wed, 30 Mar 2005) | 2 lines Changed paths: M /trunk/makedist.py Need to send the HTML file to the website too. ------------------------------------------------------------------------ r32 | bruce | 2005-03-30 23:38:30 -0600 (Wed, 30 Mar 2005) | 2 lines Changed paths: M /trunk/TODO M /trunk/spec Added texinfo and HTML documentation. ------------------------------------------------------------------------ r31 | bruce | 2005-03-30 23:23:18 -0600 (Wed, 30 Mar 2005) | 2 lines Changed paths: M /trunk/bcron.texi Added @node markers to make "makeinfo" properly parse this file. ------------------------------------------------------------------------ r30 | bruce | 2005-03-30 22:59:42 -0600 (Wed, 30 Mar 2005) | 3 lines Changed paths: M /trunk/TODO Whoops, I already handle the case where MAILTO="" by redirecting all output to /dev/null. ------------------------------------------------------------------------ r29 | bruce | 2005-03-30 22:57:57 -0600 (Wed, 30 Mar 2005) | 3 lines Changed paths: M /trunk/NEWS M /trunk/bcron-update.c bcron-update internally strips slashes from paths to prevent problems with doubled or trailing slashes in arguments. ------------------------------------------------------------------------ r28 | bruce | 2005-03-30 22:29:26 -0600 (Wed, 30 Mar 2005) | 2 lines Changed paths: M /trunk/README.in M /trunk/TODO M /trunk/spec Some documentation touchups, adding much needed information to the README. ------------------------------------------------------------------------ r27 | bruce | 2005-03-30 15:43:02 -0600 (Wed, 30 Mar 2005) | 2 lines Changed paths: M /trunk/bcron.texi Updated documentation for pre-parsed file format. ------------------------------------------------------------------------ r26 | bruce | 2005-03-30 12:31:09 -0600 (Wed, 30 Mar 2005) | 2 lines Changed paths: M /trunk/TODO M /trunk/crontab.5 Added crontab.5 man page. ------------------------------------------------------------------------ r25 | bruce | 2004-12-06 17:08:39 -0600 (Mon, 06 Dec 2004) | 2 lines Changed paths: M /trunk/bcron.h M /trunk/crontab.c M /trunk/crontabs.c Renamed crontab_load to crontab_import. ------------------------------------------------------------------------ r24 | bruce | 2004-12-06 17:04:03 -0600 (Mon, 06 Dec 2004) | 2 lines Changed paths: M /trunk/bcron-spool.c M /trunk/bcron-update.c Mark the saved crontabs as read-only. ------------------------------------------------------------------------ r23 | bruce | 2004-12-06 16:04:05 -0600 (Mon, 06 Dec 2004) | 3 lines Changed paths: M /trunk/NEWS M /trunk/bcron-exec.c Reordered some things in bcron-exec to provide error emails more often when jobs fail to run because of system errors. ------------------------------------------------------------------------ r22 | bruce | 2004-12-06 15:41:37 -0600 (Mon, 06 Dec 2004) | 2 lines Changed paths: M /trunk/NEWS M /trunk/VERSION Bumped version to 0.08 ------------------------------------------------------------------------ r21 | bruce | 2004-12-06 14:29:42 -0600 (Mon, 06 Dec 2004) | 2 lines Changed paths: M /trunk/NEWS M /trunk/timespec-parse.c Fixed crash in parsing of faulty timespecs. ------------------------------------------------------------------------ r20 | bruce | 2004-12-05 16:30:45 -0600 (Sun, 05 Dec 2004) | 2 lines Changed paths: M /trunk/NEWS M /trunk/bcron-exec.c Added sending email notification if a job was killed. ------------------------------------------------------------------------ r19 | bruce | 2004-12-05 16:08:41 -0600 (Sun, 05 Dec 2004) | 2 lines Changed paths: M /trunk/NEWS M /trunk/crontab.c Added logging of warnings when crontabs fail to be parsed properly. ------------------------------------------------------------------------ r18 | bruce | 2004-12-05 15:23:27 -0600 (Sun, 05 Dec 2004) | 2 lines Changed paths: M /trunk/NEWS M /trunk/VERSION Bumped the version to 0.07 ------------------------------------------------------------------------ r17 | bruce | 2004-11-03 14:36:04 -0600 (Wed, 03 Nov 2004) | 3 lines Changed paths: M /trunk/NEWS M /trunk/TODO M /trunk/bcron-exec.c bcron-exec now waits until all the programs its running have exited so that email gets sent properly. ------------------------------------------------------------------------ r16 | bruce | 2004-11-03 12:18:26 -0600 (Wed, 03 Nov 2004) | 2 lines Changed paths: M /trunk/NEWS M /trunk/bcron-spool.c M /trunk/bcron.texi M /trunk/bcrontab.c Added support for listing all system crontabs. ------------------------------------------------------------------------ r15 | bruce | 2004-11-03 12:17:51 -0600 (Wed, 03 Nov 2004) | 2 lines Changed paths: M /trunk/NEWS M /trunk/VERSION Bumped version to 0.06 ------------------------------------------------------------------------ r14 | bruce | 2004-10-31 01:32:31 -0600 (Sun, 31 Oct 2004) | 3 lines Changed paths: M /trunk/bcron-exec.c Reformatted the die calls; rewrote the system call logic to check against 0 instead of -1. ------------------------------------------------------------------------ r13 | bruce | 2004-10-31 01:29:46 -0600 (Sun, 31 Oct 2004) | 2 lines Changed paths: M /trunk/bcron-exec.c Switch to using the much simpler, and more portable, initgroups function. ------------------------------------------------------------------------ r12 | bruce | 2004-10-31 01:23:45 -0600 (Sun, 31 Oct 2004) | 2 lines Changed paths: M /trunk/NEWS M /trunk/bcron-exec.c Set up supplemental groups. ------------------------------------------------------------------------ r11 | bruce | 2004-10-31 01:06:33 -0600 (Sun, 31 Oct 2004) | 2 lines Changed paths: M /trunk/bcron-exec.c Broke out the execution of the command into a seperate routine. ------------------------------------------------------------------------ r10 | bruce | 2004-10-30 23:48:23 -0600 (Sat, 30 Oct 2004) | 2 lines Changed paths: M /trunk/spec Fixed some problems in the RPM spec file. ------------------------------------------------------------------------ r9 | bruce | 2004-10-30 23:47:41 -0600 (Sat, 30 Oct 2004) | 2 lines Changed paths: M /trunk/NEWS M /trunk/bcron-exec.c Die with error if executing the shell or wrapper failed. ------------------------------------------------------------------------ r8 | bruce | 2004-10-30 23:46:55 -0600 (Sat, 30 Oct 2004) | 2 lines Changed paths: M /trunk/NEWS M /trunk/VERSION Bumped version up to 0.05 ------------------------------------------------------------------------ r7 | bruce | 2004-10-28 16:46:12 -0600 (Thu, 28 Oct 2004) | 2 lines Changed paths: M /trunk/spec Added post-install and pre-remove scripts to handle the services. ------------------------------------------------------------------------ r6 | bruce | 2004-10-28 16:45:36 -0600 (Thu, 28 Oct 2004) | 3 lines Changed paths: M /trunk/NEWS M /trunk/bcron-exec.c Fixed (for good this time?) the bug in bcron-exec causing delayed execution of programs ------------------------------------------------------------------------ r5 | bruce | 2004-10-28 16:45:05 -0600 (Thu, 28 Oct 2004) | 2 lines Changed paths: M /trunk/NEWS M /trunk/VERSION Bumped version to 0.04 ------------------------------------------------------------------------ r4 | bruce | 2004-10-28 11:36:09 -0600 (Thu, 28 Oct 2004) | 2 lines Changed paths: A /trunk/NEWS A /trunk/README.in A /trunk/TODO A /trunk/VERSION A /trunk/bcron-exec.8 A /trunk/bcron-exec.c A /trunk/bcron-exec=x A /trunk/bcron-sched-log.run A /trunk/bcron-sched.8 A /trunk/bcron-sched.c A /trunk/bcron-sched.run A /trunk/bcron-sched=x A /trunk/bcron-spool.8 A /trunk/bcron-spool.c A /trunk/bcron-spool.run A /trunk/bcron-spool=x A /trunk/bcron-start.8 A /trunk/bcron-start.c A /trunk/bcron-start=x A /trunk/bcron-update.8 A /trunk/bcron-update.c A /trunk/bcron-update.run A /trunk/bcron-update=x A /trunk/bcron.h A /trunk/bcron.texi A /trunk/bcron=l A /trunk/bcrontab.1 A /trunk/bcrontab.c A /trunk/bcrontab=x A /trunk/chdir.c A /trunk/connection.c A /trunk/crontab.5 A /trunk/crontab.c A /trunk/crontabs.c A /trunk/insthier.c A /trunk/job.c A /trunk/makedist.py A /trunk/ministat.c A /trunk/run.dist A /trunk/sendpacket.c A /trunk/shiftime.c A /trunk/spec A /trunk/tempfile.c A /trunk/test-sched.c A /trunk/test-sched=x A /trunk/tests A /trunk/tests/sched-dst A /trunk/tests/sched-misc A /trunk/tests/spool-delete-baduser A /trunk/tests/spool-delete-good A /trunk/tests/spool-delete-nouser A /trunk/tests/spool-filter A /trunk/tests/spool-submit A /trunk/tests/spool-submit-baduser A /trunk/tests/spool-submit-nouser A /trunk/tests.inc A /trunk/time.c A /trunk/timespec-next.c A /trunk/timespec-parse.c Initial check-in of current sources. ------------------------------------------------------------------------ r3 | bruce | 2004-10-22 13:43:46 -0600 (Fri, 22 Oct 2004) | 1 line Changed paths: A /trunk Created trunk subdirectory ------------------------------------------------------------------------ bcron-0.09/COPYING0000664000076400007640000004311010251704762013150 0ustar bruceguenter GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. bcron-0.09/chdir.c0000664000076400007640000000032410251704762013352 0ustar bruceguenter#include #include #include #include "bcron.h" int chdir_bcron(void) { const char* dir; if ((dir = getenv("BCRON_SPOOL")) == 0) dir = BCRON_SPOOL; return chdir(dir); } bcron-0.09/bcron-sched-log.run0000664000076400007640000000010010251704762015601 0ustar bruceguenter#!/bin/sh exec >/dev/null 2>&1 exec \ multilog t /var/log/bcron bcron-0.09/AUTOFILES0000664000076400007640000000012510251704762013372 0ustar bruceguenterAUTOFILES Makefile SRCFILES TARGETS conf-bgincs conf-bglibs conf-bin conf-cc conf-ld bcron-0.09/test-sched.c0000664000076400007640000000226310251704762014330 0ustar bruceguenter#include #include #include #include #include #include #include #include #include "bcron.h" const char program[] = "test-sched"; const int msg_show_pid = 0; int msg_debug_bits = 0; const char cli_help_prefix[] = "\n"; const char cli_help_suffix[] = ""; const char cli_args_usage[] = "cron-line time"; const int cli_args_min = 2; const int cli_args_max = 2; cli_option cli_options[] = { {0,0,0,0,0,0,0} }; int cli_main(int argc, char* argv[]) { time_t start; time_t next; struct crontab c; str jobstr; msg_debug_init(); timespec_next_init(); memset(&c, 0, sizeof c); jobstr.len = strlen(jobstr.s = argv[0]); jobstr.size = 0; if (!crontab_parse(&c, &jobstr, "nobody") || c.jobs == 0) usage(111, "Invalid crontab line"); if ((start = strtol(argv[1], 0, 10)) <= 0) usage(111, "Invalid timesstamp"); next = timespec_next(&c.jobs->times, start, localtime(&start)); obuf_put5s(&outbuf, "last: ", utoa(start), " ", fmttime(start), "\n"); obuf_put5s(&outbuf, "next: ", utoa(next), " ", fmttime(next), "\n"); obuf_flush(&outbuf); return 0; (void)argc; } bcron-0.09/SRCFILES0000664000076400007640000000036710251704762013261 0ustar bruceguenterbcron-exec.c bcron-sched.c bcron-spool.c bcron-start.c bcron-update.c bcron.h bcron.texi bcrontab.c chdir.c connection.c crontab.c crontabs.c insthier.c job.c ministat.c sendpacket.c tempfile.c test-sched.c time.c timespec-next.c timespec-parse.c