Import Upstream version 1.7.2

This commit is contained in:
geos_one
2025-08-06 17:45:07 +02:00
commit 8035530473
75 changed files with 33406 additions and 0 deletions

118
src/Makemodule.am Normal file
View File

@@ -0,0 +1,118 @@
# Makefile.am - two binaries crond and crontab
sbin_PROGRAMS += \
src/crond
bin_PROGRAMS += \
src/cronnext \
src/crontab
src_crond_SOURCES = \
src/cron.c \
src/database.c \
src/do_command.c \
src/job.c \
src/popen.c \
src/security.c \
src/user.c \
cronie_common.c \
$(common_src)
src_crontab_SOURCES = \
src/crontab.c \
src/security.c \
$(common_src)
src_cronnext_SOURCES = \
src/cronnext.c \
src/database.c \
src/job.c \
src/user.c \
$(common_src)
common_src = \
src/bitstring.h \
src/entry.c \
src/env.c \
src/externs.h \
src/funcs.h \
src/globals.h \
src/macros.h \
src/misc.c \
src/pathnames.h \
src/pw_dup.c \
src/structs.h
common_nodist += cron-paths.h
nodist_src_crond_SOURCES = $(common_nodist)
nodist_src_crontab_SOURCES = $(common_nodist)
nodist_src_cronnext_SOURCES = $(common_nodist)
BUILT_SOURCES += $(common_nodist)
src_crond_LDADD = $(LIBSELINUX) $(LIBPAM) $(LIBAUDIT)
src_crontab_LDADD = $(LIBSELINUX) $(LIBPAM) $(LIBAUDIT)
## if DEBUG
## noinst_PROGRAMS = debug
## endif
# This header contains all the paths.
# If they are configurable, they are declared in configure script.
# Depends on this Makefile, because it uses make variables.
# CCD 2010/09/10 added CRON_HOSTNAME for clustered-cron.
CLEANFILES += cron-paths.h
if HAS_RUNSTATE
cronpidcomment=/* directory of cron pid file */
cronpiddir=\#define CRON_PID_DIR "$(runstatedir)"
else
cronpidcomment=
cronpiddir=
endif
cron-paths.h: Makefile
@echo 'creating $@'
@sed >$@ 's/ *\\$$//' <<\END #\
/* This file has been automatically generated. Do not edit. */ \
\
#ifndef _CRON_PATHS_H_ \
#define _CRON_PATHS_H_ \
\
/* SPOOLDIR is where the crontabs live. \
* This directory will have its modtime updated \
* whenever crontab(1) changes a crontab; this is \
* the signal for cron(8) to look at each individual \
* crontab file and reload those whose modtimes are \
* newer than they were last time around (or which \
* didn't exist last time around...) \
* or it will be checked by inotify \
*/ \
#define SPOOL_DIR "$(SPOOL_DIR)" \
\
/* CRON_HOSTNAME is file in SPOOL_DIR which, if it \
* exists, and does not just contain a line matching \
* the name returned by gethostname(), causes all \
* crontabs in SPOOL_DIR to be ignored. This is \
* intended to be used when clustering hosts sharing \
* one NFS-mounted SPOOL_DIR, and where only one host \
* should use the crontab files here at any one time. \
*/ \
#define CRON_HOSTNAME ".cron.hostname" \
\
/* cron allow/deny file. At least cron.deny must \
* exist for ordinary users to run crontab. \
*/ \
#define CRON_ALLOW "$(sysconfdir)/cron.allow" \
#define CRON_DENY "$(sysconfdir)/cron.deny" \
\
$(cronpidcomment) \
$(cronpiddir) \
\
/* 4.3BSD-style crontab f.e. /etc/crontab */ \
#define SYSCRONTAB "$(SYSCRONTAB)" \
\
/* system crontab dir f.e. /etc/cron.d/ */ \
#define SYS_CROND_DIR "$(SYS_CROND_DIR)" \
\
#define SYSCONFDIR "$(sysconfdir)" \
\
#endif /* _CRON_PATHS_H_ */ \
END

141
src/bitstring.h Normal file
View File

@@ -0,0 +1,141 @@
/* $NetBSD: bitstring.h,v 1.3 2003/08/07 11:17:08 agc Exp $ */
/*
* Copyright (c) 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Paul Vixie.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)bitstring.h 8.1 (Berkeley) 7/19/93
*/
#ifndef _BITSTRING_H_
#define _BITSTRING_H_
typedef unsigned char bitstr_t;
/* internal macros */
/* byte of the bitstring bit is in */
#define _bit_byte(bit) \
((bit) >> 3)
/* mask for the bit within its byte */
#define _bit_mask(bit) \
(1 << ((bit)&0x7))
/* external macros */
/* bytes in a bitstring of nbits bits */
#define bitstr_size(nbits) \
((((nbits) - 1) >> 3) + 1)
/* allocate a bitstring */
#define bit_alloc(nbits) \
(bitstr_t *)calloc(1, \
(unsigned int)bitstr_size(nbits) * sizeof(bitstr_t))
/* allocate a bitstring on the stack */
#define bit_decl(name, nbits) \
(name)[bitstr_size(nbits)]
/* is bit N of bitstring name set? */
#define bit_test(name, bit) \
((name)[_bit_byte(bit)] & _bit_mask(bit))
/* set bit N of bitstring name */
#define bit_set(name, bit) \
(name)[_bit_byte(bit)] |= (bitstr_t)_bit_mask(bit)
/* clear bit N of bitstring name */
#define bit_clear(name, bit) \
(name)[_bit_byte(bit)] &= (bitstr_t)~_bit_mask(bit)
/* clear bits start ... stop in bitstring */
#define bit_nclear(name, start, stop) { \
register bitstr_t *_name = name; \
register int _start = start, _stop = stop; \
register int _startbyte = _bit_byte(_start); \
register int _stopbyte = _bit_byte(_stop); \
if (_startbyte == _stopbyte) { \
_name[_startbyte] &= (bitstr_t)((0xff >> (8 - (_start&0x7))) | \
(0xff << ((_stop&0x7) + 1))); \
} else { \
_name[_startbyte] &= (bitstr_t)(0xff >> (8 - (_start&0x7))); \
while (++_startbyte < _stopbyte) \
_name[_startbyte] = 0; \
_name[_stopbyte] &= (bitstr_t)(0xff << ((_stop&0x7) + 1)); \
} \
}
/* set bits start ... stop in bitstring */
#define bit_nset(name, start, stop) { \
register bitstr_t *_name = name; \
register int _start = start, _stop = stop; \
register int _startbyte = _bit_byte(_start); \
register int _stopbyte = _bit_byte(_stop); \
if (_startbyte == _stopbyte) { \
_name[_startbyte] |= (bitstr_t)((0xff << (_start&0x7)) & \
(0xff >> (7 - (_stop&0x7)))); \
} else { \
_name[_startbyte] |= (bitstr_t)(0xff << ((_start)&0x7)); \
while (++_startbyte < _stopbyte) \
_name[_startbyte] = 0xff; \
_name[_stopbyte] |= (bitstr_t)(0xff >> (7 - (_stop&0x7))); \
} \
}
/* find first bit clear in name */
#define bit_ffc(name, nbits, value) { \
register bitstr_t *_name = name; \
register int _byte, _nbits = nbits; \
register int _stopbyte = _bit_byte(_nbits), _value = -1; \
for (_byte = 0; _byte <= _stopbyte; ++_byte) \
if (_name[_byte] != 0xff) { \
_value = _byte << 3; \
for (_stopbyte = _name[_byte]; (_stopbyte&0x1); \
++_value, _stopbyte >>= 1); \
break; \
} \
*(value) = _value; \
}
/* find first bit set in name */
#define bit_ffs(name, nbits, value) { \
register bitstr_t *_name = name; \
register int _byte, _nbits = nbits; \
register int _stopbyte = _bit_byte(_nbits), _value = -1; \
for (_byte = 0; _byte <= _stopbyte; ++_byte) \
if (_name[_byte]) { \
_value = _byte << 3; \
for (_stopbyte = _name[_byte]; !(_stopbyte&0x1); \
++_value, _stopbyte >>= 1); \
break; \
} \
*(value) = _value; \
}
#endif /* !_BITSTRING_H_ */

742
src/cron.c Normal file
View File

@@ -0,0 +1,742 @@
/* Copyright 1988,1990,1993,1994 by Paul Vixie
* All rights reserved
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Modified 2010/09/12 by Colin Dean, Durham University IT Service,
* to add clustering support.
*/
#include "config.h"
#define MAIN_PROGRAM
#include <errno.h>
#include <langinfo.h>
#include <locale.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/time.h>
#include <fcntl.h>
#ifdef WITH_INOTIFY
# include <sys/inotify.h>
#endif
#include "cronie_common.h"
#include "funcs.h"
#include "globals.h"
#include "pathnames.h"
#if defined WITH_INOTIFY
int inotify_enabled;
#else
# define inotify_enabled 0
#endif
enum timejump { negative, small, medium, large };
static void usage(void) ATTRIBUTE_NORETURN,
run_reboot_jobs(cron_db *),
find_jobs(int, cron_db *, int, int, long),
set_time(int),
cron_sleep(int, cron_db *),
sigchld_handler(int),
sighup_handler(int ATTRIBUTE_UNUSED),
sigurg_handler(int ATTRIBUTE_UNUSED),
sigchld_reaper(void),
sigintterm_handler(int ATTRIBUTE_UNUSED), parse_args(int c, char *v[]);
static volatile sig_atomic_t got_sighup, got_sigchld, got_sigintterm, got_sigurg;
static int timeRunning, virtualTime, clockTime;
static long GMToff;
static int DisableInotify;
#if defined WITH_INOTIFY
/*
* Note that inotify isn't safe to use with clustering, as changes made
* to a shared filesystem on one system cannot be relied on to be notified
* on another system, so use of inotify is disabled at runtime if run with
* clustering enabled.
*/
# if defined ENABLE_SYSCRONTAB
# define NUM_WATCHES 3
int wd[NUM_WATCHES];
const char *watchpaths[NUM_WATCHES] = {SPOOL_DIR, SYS_CROND_DIR, SYSCRONTAB};
# else
# define NUM_WATCHES 2
int wd[NUM_WATCHES];
const char *watchpaths[NUM_WATCHES] = {SPOOL_DIR, SYS_CROND_DIR};
# endif
static void reset_watches(void) {
size_t i;
for (i = 0; i < sizeof (wd) / sizeof (wd[0]); ++i) {
wd[i] = -2;
}
}
void set_cron_unwatched(int fd) {
size_t i;
for (i = 0; i < sizeof (wd) / sizeof (wd[0]); ++i) {
if (wd[i] > 0) {
inotify_rm_watch(fd, wd[i]);
wd[i] = -1;
}
}
}
void set_cron_watched(int fd) {
pid_t pid = getpid();
size_t i;
if (fd < 0) {
inotify_enabled = 0;
return;
}
for (i = 0; i < sizeof (wd) / sizeof (wd[0]); ++i) {
int w;
w = inotify_add_watch(fd, watchpaths[i],
IN_CREATE | IN_CLOSE_WRITE | IN_ATTRIB | IN_MODIFY | IN_MOVED_TO |
IN_MOVED_FROM | IN_MOVE_SELF | IN_DELETE | IN_DELETE_SELF);
if (w < 0 && errno != ENOENT) {
if (wd[i] != -1) {
log_it("CRON", pid, "This directory or file can't be watched",
watchpaths[i], errno);
log_it("CRON", pid, "INFO", "running without inotify support",
0);
}
inotify_enabled = 0;
set_cron_unwatched(fd);
return;
}
wd[i] = w;
}
if (!inotify_enabled) {
log_it("CRON", pid, "INFO", "running with inotify support", 0);
}
inotify_enabled = 1;
}
#endif
static void handle_signals(cron_db * database) {
if (got_sighup || got_sigurg) {
got_sighup = 0;
got_sigurg = 0;
#if defined WITH_INOTIFY
/* watches must be reinstated on reload */
if (inotify_enabled && (EnableClustering != 1)) {
set_cron_unwatched(database->ifd);
reset_watches();
inotify_enabled = 0;
}
#endif
database->mtime = (time_t) 0;
log_close();
}
if (got_sigchld) {
got_sigchld = 0;
sigchld_reaper();
}
}
static void usage(void) {
const char **dflags;
fprintf(stderr, "Usage:\n");
fprintf(stderr, " %s [options]\n", ProgramName);
fprintf(stderr, "\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " -h print this message \n");
fprintf(stderr, " -i daemon runs without inotify support\n");
fprintf(stderr, " -m <comm> off, or specify preferred client for sending mails\n");
fprintf(stderr, " -n run in foreground\n");
fprintf(stderr, " -f run in foreground, the same as -n\n");
fprintf(stderr, " -p permit any crontab\n");
fprintf(stderr, " -P inherit PATH from environment instead of using default value");
fprintf(stderr, " of \"%s\"\n", _PATH_STDPATH);
fprintf(stderr, " -c enable clustering support\n");
fprintf(stderr, " -s log into syslog instead of sending mails\n");
fprintf(stderr, " -V print version and exit\n");
fprintf(stderr, " -x <flag> print debug information\n");
fprintf(stderr, "\n");
fprintf(stderr, "Debugging flags are: ");
for (dflags = DebugFlagNames; *dflags; dflags++)
fprintf(stderr, "%s%s", *dflags, dflags[1] ? "," : "\n");
exit(ERROR_EXIT);
}
int main(int argc, char *argv[]) {
struct sigaction sact;
cron_db database;
int fd;
char *cs;
pid_t pid = getpid();
long oldGMToff;
struct timeval tv;
struct timezone tz;
char buf[256];
if ((ProgramName=strrchr(argv[0], '/')) == NULL) {
ProgramName = argv[0];
}
else {
++ProgramName;
}
MailCmd[0] = '\0';
cron_default_mail_charset[0] = '\0';
setlocale(LC_ALL, "");
#if defined(BSD)
setlinebuf(stdout);
setlinebuf(stderr);
#endif
SyslogOutput = 0;
NoFork = 0;
ChangePath = 1;
parse_args(argc, argv);
memset((char *) &sact, 0, sizeof sact);
sigemptyset(&sact.sa_mask);
sact.sa_flags = 0;
#ifdef SA_RESTART
sact.sa_flags |= SA_RESTART;
#endif
sact.sa_handler = sigchld_handler;
(void) sigaction(SIGCHLD, &sact, NULL);
sact.sa_handler = sighup_handler;
(void) sigaction(SIGHUP, &sact, NULL);
sact.sa_handler = sigintterm_handler;
(void) sigaction(SIGINT, &sact, NULL);
(void) sigaction(SIGTERM, &sact, NULL);
sact.sa_handler = sigurg_handler;
(void) sigaction(SIGURG, &sact, NULL);
acquire_daemonlock(0);
set_cron_uid();
check_spool_dir();
if (ChangePath) {
if (setenv("PATH", _PATH_STDPATH, 1) < 0) {
log_it("CRON", pid, "DEATH", "can't setenv PATH",
errno);
exit(1);
}
}
/* Get the default locale character set for the mail
* "Content-Type: ...; charset=" header
*/
setlocale(LC_ALL, ""); /* set locale to system defaults or to
* that specified by any LC_* env vars */
if ((cs = nl_langinfo(CODESET)) != NULL)
strncpy(cron_default_mail_charset, cs, MAX_ENVSTR-1);
else
strcpy(cron_default_mail_charset, "US-ASCII");
/* if there are no debug flags turned on, fork as a daemon should.
*/
if (DebugFlags) {
#if DEBUGGING
(void) fprintf(stderr, "[%ld] cron started\n", (long) getpid());
#endif
}
else if (NoFork == 0) {
switch (fork()) {
case -1:
log_it("CRON", pid, "DEATH", "can't fork", errno);
exit(0);
break;
case 0:
/* child process */
(void) setsid();
if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) >= 0) {
(void) dup2(fd, STDIN);
(void) dup2(fd, STDOUT);
(void) dup2(fd, STDERR);
if (fd != STDERR)
(void) close(fd);
}
break;
default:
/* parent process should just die */
_exit(0);
}
}
log_it("CRON", getpid(), "STARTUP", PACKAGE_VERSION, 0);
if (!SyslogOutput && MailCmd[0] == '\0' && access("/usr/sbin/sendmail", X_OK) != 0) {
SyslogOutput=1;
log_it("CRON", pid, "INFO","Syslog will be used instead of sendmail.", 0);
}
pid = getpid();
/* obtain a random scaling factor for RANDOM_DELAY */
if (gettimeofday(&tv, &tz) != 0)
tv.tv_usec = 0;
srandom((unsigned int)(pid + tv.tv_usec));
RandomScale = (double)random() / (double)(1lu << 31);
snprintf(buf, sizeof(buf), "RANDOM_DELAY will be scaled with factor %d%% if used.", (int)(RandomScale*100));
log_it("CRON", pid, "INFO", buf, 0);
acquire_daemonlock(0);
fd = -1;
#if defined WITH_INOTIFY
if (DisableInotify || EnableClustering) {
log_it("CRON", getpid(), "No inotify - daemon runs with -i or -c option",
"", 0);
}
else {
reset_watches();
database.ifd = fd = inotify_init();
fcntl(fd, F_SETFD, FD_CLOEXEC);
if (fd < 0)
log_it("CRON", pid, "INFO", "Inotify init failed", errno);
set_cron_watched(fd);
}
#endif
database.head = NULL;
database.tail = NULL;
database.mtime = (time_t) 0;
load_database(&database);
set_time(TRUE);
run_reboot_jobs(&database);
timeRunning = virtualTime = clockTime;
oldGMToff = GMToff;
/*
* Too many clocks, not enough time (Al. Einstein)
* These clocks are in minutes since the epoch, adjusted for timezone.
* virtualTime: is the time it *would* be if we woke up
* promptly and nobody ever changed the clock. It is
* monotonically increasing... unless a timejump happens.
* At the top of the loop, all jobs for 'virtualTime' have run.
* timeRunning: is the time we last awakened.
* clockTime: is the time when set_time was last called.
*/
while (!got_sigintterm) {
int timeDiff;
enum timejump wakeupKind;
/* ... wait for the time (in minutes) to change ... */
do {
cron_sleep(timeRunning + 1, &database);
set_time(FALSE);
} while (!got_sigintterm && clockTime == timeRunning);
if (got_sigintterm)
break;
timeRunning = clockTime;
/*
* Calculate how the current time differs from our virtual
* clock. Classify the change into one of 4 cases.
*/
timeDiff = timeRunning - virtualTime;
check_orphans(&database);
#if defined WITH_INOTIFY
if (inotify_enabled) {
check_inotify_database(&database);
}
else {
if (load_database(&database) && (EnableClustering != 1))
/* try reinstating the watches */
set_cron_watched(fd);
}
#else
load_database(&database);
#endif
/* shortcut for the most common case */
if (timeDiff == 1) {
virtualTime = timeRunning;
oldGMToff = GMToff;
find_jobs(virtualTime, &database, TRUE, TRUE, oldGMToff);
}
else {
if (timeDiff > (3 * MINUTE_COUNT) || timeDiff < -(3 * MINUTE_COUNT))
wakeupKind = large;
else if (timeDiff > 5)
wakeupKind = medium;
else if (timeDiff > 0)
wakeupKind = small;
else
wakeupKind = negative;
switch (wakeupKind) {
case small:
/*
* case 1: timeDiff is a small positive number
* (wokeup late) run jobs for each virtual
* minute until caught up.
*/
Debug(DSCH, ("[%ld], normal case %d minutes to go\n",
(long) pid, timeDiff));
do {
if (job_runqueue())
sleep(10);
virtualTime++;
if (virtualTime >= timeRunning)
/* always run also the other timezone jobs in the last step */
oldGMToff = GMToff;
find_jobs(virtualTime, &database, TRUE, TRUE, oldGMToff);
} while (virtualTime < timeRunning);
break;
case medium:
/*
* case 2: timeDiff is a medium-sized positive
* number, for example because we went to DST
* run wildcard jobs once, then run any
* fixed-time jobs that would otherwise be
* skipped if we use up our minute (possible,
* if there are a lot of jobs to run) go
* around the loop again so that wildcard jobs
* have a chance to run, and we do our
* housekeeping.
*/
Debug(DSCH, ("[%ld], DST begins %d minutes to go\n",
(long) pid, timeDiff));
/* run wildcard jobs for current minute */
find_jobs(timeRunning, &database, TRUE, FALSE, GMToff);
/* run fixed-time jobs for each minute missed */
do {
if (job_runqueue())
sleep(10);
virtualTime++;
if (virtualTime >= timeRunning)
/* always run also the other timezone jobs in the last step */
oldGMToff = GMToff;
find_jobs(virtualTime, &database, FALSE, TRUE, oldGMToff);
set_time(FALSE);
} while (virtualTime < timeRunning && clockTime == timeRunning);
break;
case negative:
/*
* case 3: timeDiff is a small or medium-sized
* negative num, eg. because of DST ending.
* Just run the wildcard jobs. The fixed-time
* jobs probably have already run, and should
* not be repeated. Virtual time does not
* change until we are caught up.
*/
Debug(DSCH, ("[%ld], DST ends %d minutes to go\n",
(long) pid, timeDiff));
find_jobs(timeRunning, &database, TRUE, FALSE, GMToff);
break;
default:
/*
* other: time has changed a *lot*,
* jump virtual time, and run everything
*/
Debug(DSCH, ("[%ld], clock jumped\n", (long) pid));
virtualTime = timeRunning;
oldGMToff = GMToff;
find_jobs(timeRunning, &database, TRUE, TRUE, GMToff);
}
}
/* Jobs to be run (if any) are loaded; clear the queue. */
job_runqueue();
handle_signals(&database);
}
#if defined WITH_INOTIFY
if (inotify_enabled && (EnableClustering != 1))
set_cron_unwatched(fd);
if (fd >= 0 && close(fd) < 0)
log_it("CRON", pid, "INFO", "Inotify close failed", errno);
#endif
log_it("CRON", pid, "INFO", "Shutting down", 0);
(void) unlink(_PATH_CRON_PID);
return 0;
}
static void run_reboot_jobs(cron_db * db) {
user *u;
entry *e;
int reboot;
pid_t pid = getpid();
/* lock exist - skip reboot jobs */
if (access(REBOOT_LOCK, F_OK) == 0) {
log_it("CRON", pid, "INFO",
"@reboot jobs will be run at computer's startup.", 0);
return;
}
/* lock doesn't exist - create lock, run reboot jobs */
if ((reboot = creat(REBOOT_LOCK, S_IRUSR & S_IWUSR)) < 0)
log_it("CRON", pid, "INFO", "Can't create lock for reboot jobs.",
errno);
else
close(reboot);
for (u = db->head; u != NULL; u = u->next) {
for (e = u->crontab; e != NULL; e = e->next) {
if (e->flags & WHEN_REBOOT)
job_add(e, u);
}
}
(void) job_runqueue();
}
static void find_jobs(int vtime, cron_db * db, int doWild, int doNonWild, long vGMToff) {
char *orig_tz, *job_tz;
struct tm *tm;
int minute, hour, dom, month, dow;
user *u;
entry *e;
/* The support for the job-specific timezones is not perfect. There will
* be jobs missed or run twice during the DST change in the job timezone.
* It is recommended not to schedule any jobs during the hour when
* the DST changes happen if job-specific timezones are used.
*
* Make 0-based values out of tm values so we can use them as indices
*/
#define maketime(tz1, tz2) do { \
char *t = tz1; \
if (t != NULL && *t != '\0') { \
setenv("TZ", t, 1); \
tm = localtime(&virtualGMTSecond); \
} else { if ((tz2) != NULL) \
setenv("TZ", (tz2), 1); \
else \
unsetenv("TZ"); \
tm = gmtime(&virtualSecond); \
} \
minute = tm->tm_min -FIRST_MINUTE; \
hour = tm->tm_hour -FIRST_HOUR; \
dom = tm->tm_mday -FIRST_DOM; \
month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; \
dow = tm->tm_wday -FIRST_DOW; \
} while (0)
orig_tz = getenv("TZ");
/* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
* first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
* on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
* is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre.
* like many bizarre things, it's the standard.
*/
for (u = db->head; u != NULL; u = u->next) {
for (e = u->crontab; e != NULL; e = e->next) {
time_t virtualSecond = (time_t)(vtime - e->delay) * (time_t)SECONDS_PER_MINUTE;
time_t virtualGMTSecond = virtualSecond - vGMToff;
job_tz = env_get("CRON_TZ", e->envp);
maketime(job_tz, orig_tz);
/* here we test whether time is NOW */
if (bit_test(e->minute, minute) &&
bit_test(e->hour, hour) &&
bit_test(e->month, month) &&
(((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
? (bit_test(e->dow, dow) && bit_test(e->dom, dom))
: (bit_test(e->dow, dow) || bit_test(e->dom, dom))
)
) {
if (job_tz != NULL && vGMToff != GMToff)
/* do not try to run the jobs from different timezones
* during the DST switch of the default timezone.
*/
continue;
if ((doNonWild &&
!(e->flags & (MIN_STAR | HR_STAR))) ||
(doWild && (e->flags & (MIN_STAR | HR_STAR))))
job_add(e, u); /*will add job, if it isn't in queue already for NOW. */
}
}
}
if (orig_tz != NULL)
setenv("TZ", orig_tz, 1);
else
unsetenv("TZ");
}
/*
* Set StartTime and clockTime to the current time.
* These are used for computing what time it really is right now.
* Note that clockTime is a unix wallclock time converted to minutes.
*/
static void set_time(int initialize) {
struct tm tm;
static int isdst;
StartTime = time(NULL);
/* We adjust the time to GMT so we can catch DST changes. */
tm = *localtime(&StartTime);
if (initialize || tm.tm_isdst != isdst) {
isdst = tm.tm_isdst;
GMToff = get_gmtoff(&StartTime, &tm);
Debug(DSCH, ("[%ld] GMToff=%ld\n", (long) getpid(), (long) GMToff));
}
clockTime = (int)((StartTime + GMToff) / (time_t) SECONDS_PER_MINUTE);
}
/*
* Try to just hit the next minute.
*/
static void cron_sleep(int target, cron_db * db) {
time_t t1, t2;
int seconds_to_wait;
t1 = time(NULL) + GMToff;
seconds_to_wait = (int)((time_t)target * SECONDS_PER_MINUTE - t1);
/* always sleep at least once unless time goes backwards */
if (seconds_to_wait == 0)
seconds_to_wait = 1;
Debug(DSCH, ("[%ld] Target time=%ld, sec-to-wait=%d\n",
(long) getpid(), (long) target * SECONDS_PER_MINUTE,
seconds_to_wait));
while (seconds_to_wait > 0 && seconds_to_wait < 65) {
sleep((unsigned int) seconds_to_wait);
if (got_sigintterm)
return;
/*
* Check to see if we were interrupted by a signal.
* If so, service the signal(s) then continue sleeping
* where we left off.
*/
handle_signals(db);
t2 = time(NULL) + GMToff;
seconds_to_wait -= (int) (t2 - t1);
t1 = t2;
}
}
static void sighup_handler(int x ATTRIBUTE_UNUSED) {
got_sighup = 1;
}
static void sigchld_handler(int x ATTRIBUTE_UNUSED) {
got_sigchld = 1;
}
static void sigintterm_handler(int x ATTRIBUTE_UNUSED) {
got_sigintterm = 1;
}
static void sigurg_handler(int x ATTRIBUTE_UNUSED) {
got_sigurg = 1;
}
static void sigchld_reaper(void) {
WAIT_T waiter;
PID_T pid;
do {
pid = waitpid(-1, &waiter, WNOHANG);
switch (pid) {
case -1:
if (errno == EINTR)
continue;
Debug(DPROC, ("[%ld] sigchld...no children\n", (long) getpid()));
break;
case 0:
Debug(DPROC, ("[%ld] sigchld...no dead kids\n", (long) getpid()));
break;
default:
Debug(DPROC,
("[%ld] sigchld...pid #%ld died, stat=%d\n",
(long) getpid(), (long) pid, WEXITSTATUS(waiter)));
break;
}
} while (pid > 0);
}
static void parse_args(int argc, char *argv[]) {
int argch;
while (-1 != (argch = getopt(argc, argv, "hnfpsiPx:m:cV"))) {
switch (argch) {
case 'x':
if (!set_debug_flags(optarg))
usage();
break;
case 'n':
case 'f':
NoFork = 1;
break;
case 'p':
PermitAnyCrontab = 1;
break;
case 's':
SyslogOutput = 1;
break;
case 'i':
DisableInotify = 1;
break;
case 'P':
ChangePath = 0;
break;
case 'm':
strncpy(MailCmd, optarg, MAX_COMMAND);
break;
case 'c':
EnableClustering = 1;
break;
case 'V':
puts(PACKAGE_STRING);
exit(EXIT_SUCCESS);
case 'h':
default:
usage();
break;
}
}
}

435
src/cronnext.c Normal file
View File

@@ -0,0 +1,435 @@
/*
cronnext - calculate the time cron will execute the next job
Copyright (C) 2016 Marco Migliori <sgerwk@aol.com>
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
The GNU General Public License can also be found in the file
`COPYING' that comes with the Anacron source distribution.
*/
#include "config.h"
#define MAIN_PROGRAM
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <pwd.h>
#include "globals.h"
#include "funcs.h"
#include "cron-paths.h"
/* flags to crontab search */
#define ENTRIES 0x01 // print entries
#define CRONTABS 0x02 // print crontabs
#define SYSTEM 0x04 // include system crontab
#define ALLJOBS 0x08 // print all jobs in interval
#ifdef WITH_INOTIFY
void set_cron_watched(int fd) {
/* empty stub */
(void)fd;
}
#endif
void do_command(entry *e, user *u) {
/* empty stub */
(void)e;
(void)u;
}
#ifdef WITH_SELINUX
int get_security_context(const char *name, int crontab_fd,
security_context_t *rcontext, const char *tabname) {
/* empty stub */
(void)name;
(void)crontab_fd;
(void)tabname;
*rcontext = NULL;
return 0;
}
void free_security_context(security_context_t *scontext) {
/* empty stub */
(void)scontext;
}
#endif
/*
* print entry flags
*/
const char *flagname[]= {
"MIN_STAR",
"HR_STAR",
"DOM_STAR",
"DOW_STAR",
"WHEN_REBOOT",
"DONT_LOG"
};
void printflags(char *indent, int flags) {
size_t f;
int first = 1;
printf("%s flagnames:", indent);
for (f = 0; f < sizeof(flagname)/sizeof(char *); f++)
if (flags & (int)1 << f) {
printf("%s%s", first ? " " : "|", flagname[f]);
first = 0;
}
printf("\n");
}
/*
* print a crontab entry
*/
void printentry(char *indent, entry *e, time_t next) {
printf("%s - user: %s\n", indent, e->pwd->pw_name);
printf("%s cmd: \"%s\"\n", indent, e->cmd);
printf("%s flags: 0x%02X\n", indent, e->flags);
printflags(indent, e->flags);
printf("%s delay: %d\n", indent, e->delay);
printf("%s next: %ld\n", indent, (long)next);
printf("%s nextstring: ", indent);
printf("%s", asctime(localtime(&next)));
}
/*
* print a crontab data
*/
void printcrontab(user *u) {
printf(" - user: \"%s\"\n", u->name);
printf(" crontab: %s\n", u->tabname);
printf(" system: %d\n", u->system);
printf(" entries:\n");
}
/*
* basic algorithm: iterate over time from now to 8 year ahead in default steps
* of 1 minute, checking whether time matches a crontab entry at each step (8
* years is the largest interval between two crontab matches)
*
* to save iterations, use larger steps if month or day don't match the entry:
* - if the month doesn't match, skip to 00:00 of the first day of next month
* - for the day, avoid the complication of the different length of months: if
* neither the day nor the next day match, increase time of one day
*/
/*
* check whether time matches day of month and/or day of week; this requires
* checking dom if dow=*, dow if dom=*, either one otherwise; see comment "the
* dom/dow situation is odd..." in cron.c
*/
int matchday(entry *e, time_t time) {
struct tm current;
localtime_r(&time, &current);
if (e->flags & DOW_STAR)
return bit_test(e->dom, current.tm_mday - 1);
if (e->flags & DOM_STAR)
return bit_test(e->dow, current.tm_wday);
return bit_test(e->dom, current.tm_mday - 1) ||
bit_test(e->dow, current.tm_wday);
}
/*
* next time matching a crontab entry
*/
time_t nextmatch(entry *e, time_t start, time_t end) {
time_t time;
struct tm current;
for (time = start; time <= end; ) {
localtime_r(&time, &current);
/* month doesn't match: move to 1st of next month */
if (!bit_test(e->month, current.tm_mon)) {
current.tm_mon++;
if (current.tm_mon >= 12) {
current.tm_year++;
current.tm_mon = 0;
}
current.tm_mday = 1;
current.tm_hour = 0;
current.tm_min = 0;
time = mktime(&current);
continue;
}
/* neither time nor time+1day match day: increase 1 day */
if (!matchday(e, time) && !matchday(e, time + 24 * 60 * 60)) {
time += 24 * 60 * 60;
continue;
}
/* if time matches, return time;
* check for month is redundant, but check for day is
* necessary because we only know that either time
* or time+1day match */
if (bit_test(e->month, current.tm_mon) &&
matchday(e, time) &&
bit_test(e->hour, current.tm_hour) &&
bit_test(e->minute, current.tm_min)
)
return time;
/* skip to next minute */
time += 60;
}
return -1;
}
/*
* match a user against a list
*/
int matchuser(char *user_name, char *list) {
char *pos;
size_t l = strlen(user_name);
for (pos = list; (pos = strstr(pos, user_name)) != NULL; pos += l) {
if ((pos != list) && (*(pos - 1) != ','))
continue;
if ((pos[l] != '\0') && (pos[l] != ','))
continue;
return 1;
}
return 0;
}
/*
* find the next scheduled job
*/
time_t cronnext(cron_db database,
time_t start, time_t end,
char *include, char *exclude, char *command, int flags) {
time_t closest, next;
user *u;
entry *e;
char *indent = "";
if (flags & CRONTABS) {
printf("crontabs:\n");
indent = " ";
}
else if (flags & ALLJOBS)
printf("jobs:\n");
/* find the next scheduled time */
closest = -1;
for (u = database.head; u; u = u->next) {
if (include && !matchuser(u->name, include))
continue;
if (exclude && matchuser(u->name, exclude))
continue;
if (!(flags & SYSTEM) && u->system)
continue;
if (flags & CRONTABS)
printcrontab(u);
for (e = u->crontab; e; e = e->next) {
if (command && strstr(e->cmd, command) == NULL)
continue;
for (next = nextmatch(e, start, end);
next <= end;
next = nextmatch(e, next + 60, end)) {
if (next < 0)
break;
if (closest < 0 || next < closest)
closest = next;
if (flags & ENTRIES)
printentry(indent, e, next);
if (! (flags & ALLJOBS))
break;
}
}
}
return closest;
}
/*
* load installed crontabs and/or crontab files
*/
cron_db database(int installed, char **additional) {
cron_db db = {NULL, NULL, (time_t) 0};
struct passwd pw;
int fd;
struct stat ss;
user *u;
if (installed)
load_database(&db);
for ( ; *additional != NULL; additional++) {
fd = open(*additional, O_RDONLY);
if (fd == -1) {
perror(*additional);
continue;
}
fstat(fd, &ss);
if (S_ISDIR(ss.st_mode)) {
fprintf(stderr, "%s is a directory - skipping\n", *additional);
close(fd);
continue;
}
memset(&pw, 0, sizeof(pw));
pw.pw_name = *additional;
pw.pw_passwd = "";
pw.pw_dir = ".";
u = load_user(fd, &pw, *additional, *additional, *additional);
if (u == NULL) {
printf("cannot load crontab %s\n", *additional);
continue;
}
link_user(&db, u);
}
return db;
}
void usage() {
fprintf(stderr, "Find the time of the next scheduled cron job.\n");
fprintf(stderr, "Usage:\n");
fprintf(stderr, " cronnext [options] [file ...]\n");
fprintf(stderr, "\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " -i users include only the crontab of these users\n");
fprintf(stderr, " -e users exclude the crontab of these users\n");
fprintf(stderr, " -s do not include the system crontab\n");
fprintf(stderr, " -a examine installed crontabs even if files are given\n");
fprintf(stderr, " -t time start from this time (seconds since epoch)\n");
fprintf(stderr, " -q time end check at this time (seconds since epoch)\n");
fprintf(stderr, " -j cmd only check jobs that contain cmd as a substring\n");
fprintf(stderr, " -l print next jobs to be executed\n");
fprintf(stderr, " -c print next execution of each job\n");
fprintf(stderr, " -f print all jobs executed in the given interval\n");
fprintf(stderr, " -h this help\n");
fprintf(stderr, " -V print version and exit\n");
}
/*
* main
*/
int main(int argn, char *argv[]) {
int opt;
char *include, *exclude, *command;
int flags;
time_t start, next, end = 0;
int endtime, printjobs;
cron_db db;
int installed = 0;
include = NULL;
exclude = NULL;
command = NULL;
flags = SYSTEM;
endtime = 0;
printjobs = 0;
start = (time(NULL) + 59) / 60 * 60;
while (-1 != (opt = getopt(argn, argv, "i:e:ast:q:j:lcfhV"))) {
switch (opt) {
case 'i':
include = optarg;
break;
case 'e':
exclude = optarg;
break;
case 'a':
installed = 1;
break;
case 's':
flags &= ~SYSTEM;
break;
case 't':
start = (atoi(optarg) + 59) / 60 * 60;
break;
case 'q':
end = atoi(optarg) / 60 * 60;
endtime = 1;
break;
case 'j':
command = optarg;
break;
case 'l':
printjobs = 1;
break;
case 'c':
flags |= ENTRIES | CRONTABS;
break;
case 'f':
flags |= ALLJOBS | ENTRIES;
break;
case 'h':
usage();
return EXIT_SUCCESS;
case 'V':
puts(PACKAGE_STRING);
return EXIT_SUCCESS;
default:
fprintf(stderr, "unrecognized option: %s\n",
argv[optind - 1]);
usage();
exit(EXIT_FAILURE);
}
}
if (flags & ALLJOBS && !endtime) {
fprintf(stderr, "no ending time specified: -f requires -q\n");
usage();
exit(EXIT_FAILURE);
}
/* maximum match interval is 8 years:
* crontab has '* * 29 2 *' and we are on 1 March 2096:
* next matching time will be 29 February 2104 */
if (!endtime)
end = start + 8 * 12 * 31 * 24 * 60 * 60;
/* debug cron */
if (flags & CRONTABS) {
printf("spool: %s\n", SPOOL_DIR);
set_debug_flags("");
}
/* "load,pars" for debugging loading and parsing, "" for nothing
see globals.h for symbolic names and macros.h for meaning */
/* load database */
db = database(installed || argv[optind] == NULL, argv + optind);
/* find time of next scheduled command */
next = cronnext(db, start, end, include, exclude, command, flags);
/* print time */
if (next == -1)
return EXIT_FAILURE;
else
printf("next: %ld\n", (long) next);
/* print next jobs */
if (printjobs) {
printf("nextjobs:\n");
cronnext(db, next, next, include, exclude, command, (flags & SYSTEM) | ENTRIES);
}
return EXIT_SUCCESS;
}

1241
src/crontab.c Normal file

File diff suppressed because it is too large Load Diff

682
src/database.c Normal file
View File

@@ -0,0 +1,682 @@
/* Copyright 1988,1990,1993,1994 by Paul Vixie
* All rights reserved
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* vix 26jan87 [RCS has the log]
*/
/*
* Modified 2010/09/12 by Colin Dean, Durham University IT Service,
* to add clustering support.
*/
#include "config.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef WITH_INOTIFY
# include <sys/inotify.h>
#endif
#include "funcs.h"
#include "globals.h"
#include "pathnames.h"
/* size of the event structure, not counting name */
#define EVENT_SIZE (sizeof (struct inotify_event))
/* reasonable guess as to size of 1024 events */
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
static void overwrite_database(cron_db *, cron_db *);
static void process_crontab(const char *, const char *,
const char *, cron_db *, cron_db *);
static int not_a_crontab(DIR_T * dp);
/* return 1 if we should skip this file */
static void max_mtime(const char *dir_name, struct stat *max_st);
/* record max mtime of any file under dir_name in max_st */
static int
check_open(const char *tabname, const char *uname,
struct passwd *pw, time_t * mtime) {
struct stat statbuf;
int crontab_fd;
pid_t pid = getpid();
if ((crontab_fd =
open(tabname, O_RDONLY | O_NONBLOCK, 0)) == -1) {
log_it(uname, pid, "CAN'T OPEN", tabname, errno);
return (-1);
}
if (fstat(crontab_fd, &statbuf) < OK) {
log_it(uname, pid, "STAT FAILED", tabname, errno);
close(crontab_fd);
return (-1);
}
*mtime = statbuf.st_mtime;
if (PermitAnyCrontab == 0) {
if (!S_ISREG(statbuf.st_mode)) {
log_it(uname, pid, "NOT REGULAR", tabname, 0);
close(crontab_fd);
return (-1);
}
if ((statbuf.st_mode & 07533) != 0400) {
log_it(uname, pid, "BAD FILE MODE", tabname, 0);
close(crontab_fd);
return (-1);
}
if (statbuf.st_uid != ROOT_UID && (pw == NULL ||
statbuf.st_uid != pw->pw_uid ||
strcmp(uname, pw->pw_name) != 0)) {
log_it(uname, pid, "WRONG FILE OWNER", tabname, 0);
close(crontab_fd);
return (-1);
}
if (pw && statbuf.st_nlink != 1) {
log_it(uname, pid, "BAD LINK COUNT", tabname, 0);
close(crontab_fd);
return (-1);
}
}
return (crontab_fd);
}
static orphan *orphans;
static void
free_orphan(orphan *o) {
free(o->tabname);
free(o->fname);
free(o->uname);
free(o);
}
void
check_orphans(cron_db *db) {
orphan *prev_orphan = NULL;
orphan *o = orphans;
while (o != NULL) {
if (getpwnam(o->uname) != NULL) {
orphan *next = o->next;
if (prev_orphan == NULL) {
orphans = next;
} else {
prev_orphan->next = next;
}
process_crontab(o->uname, o->fname, o->tabname,
db, NULL);
/* process_crontab could have added a new orphan */
if (prev_orphan == NULL && orphans != next) {
prev_orphan = orphans;
}
free_orphan(o);
o = next;
} else {
prev_orphan = o;
o = o->next;
}
}
}
static int
find_orphan(const char *uname, const char *fname, const char *tabname) {
orphan *o;
for (o = orphans; o != NULL; o = o->next) {
if (uname && o->uname) {
if (strcmp(uname, o->uname) != 0)
continue;
} else if (uname != o->uname)
continue;
if (fname && o->fname) {
if (strcmp(fname, o->fname) != 0)
continue;
} else if (fname != o->fname)
continue;
if (tabname && o->tabname) {
if (strcmp(tabname, o->tabname) != 0)
continue;
} else if (tabname != o->tabname)
continue;
return 1;
}
return 0;
}
static void
add_orphan(const char *uname, const char *fname, const char *tabname) {
orphan *o;
if (find_orphan(uname, fname, tabname))
return;
o = calloc(1, sizeof(*o));
if (o == NULL)
return;
if (uname)
if ((o->uname=strdup(uname)) == NULL)
goto cleanup;
if (fname)
if ((o->fname=strdup(fname)) == NULL)
goto cleanup;
if (tabname)
if ((o->tabname=strdup(tabname)) == NULL)
goto cleanup;
o->next = orphans;
orphans = o;
return;
cleanup:
free_orphan(o);
}
static void
process_crontab(const char *uname, const char *fname, const char *tabname,
cron_db * new_db, cron_db * old_db) {
struct passwd *pw = NULL;
int crontab_fd = -1;
user *u = NULL;
time_t mtime;
int crond_crontab = (fname == NULL) && (strcmp(tabname, SYSCRONTAB) != 0);
if (fname == NULL) {
/* must be set to something for logging purposes.
*/
fname = "*system*";
}
else if ((pw = getpwnam(uname)) == NULL) {
/* file doesn't have a user in passwd file.
*/
log_it(uname, getpid(), "ORPHAN", "no passwd entry", 0);
add_orphan(uname, fname, tabname);
goto next_crontab;
}
if ((crontab_fd = check_open(tabname, uname, pw, &mtime)) == -1)
goto next_crontab;
mtime = TMIN(new_db->mtime, mtime);
Debug(DLOAD, ("\t%s:", fname));
if (old_db != NULL)
u = find_user(old_db, fname, crond_crontab ? tabname : NULL); /* find user in old_db */
if (u != NULL) {
/* if crontab has not changed since we last read it
* in, then we can just use our existing entry.
*/
if (u->mtime == mtime) {
Debug(DLOAD, (" [no change, using old data]"));
unlink_user(old_db, u);
link_user(new_db, u);
goto next_crontab;
}
/* before we fall through to the code that will reload
* the user, let's deallocate and unlink the user in
* the old database. This is more a point of memory
* efficiency than anything else, since all leftover
* users will be deleted from the old database when
* we finish with the crontab...
*/
Debug(DLOAD, (" [delete old data]"));
unlink_user(old_db, u);
free_user(u);
log_it(fname, getpid(), "RELOAD", tabname, 0);
}
u = load_user(crontab_fd, pw, uname, fname, tabname); /* read the file */
crontab_fd = -1; /* load_user takes care of closing the file */
if (u != NULL) {
u->mtime = mtime;
link_user(new_db, u);
}
next_crontab:
if (crontab_fd != -1) {
Debug(DLOAD, (" [done]\n"));
close(crontab_fd);
}
}
static int
cluster_host_is_local(void)
{
char filename[NAME_MAX+1];
int is_local;
FILE *f;
char hostname[MAXHOSTNAMELEN], myhostname[MAXHOSTNAMELEN];
if (!EnableClustering)
return (1);
/* to allow option of NFS-mounting SPOOL_DIR on a cluster of
* hosts and to only use crontabs here on any one host at a
* time, allow for existence of a CRON_HOSTNAME file, and if
* it doesn't exist, or exists but does not contain this
* host's hostname, then skip the crontabs.
*
* Note: for safety's sake, no CRON_HOSTNAME file means skip,
* otherwise its accidental deletion could result in multiple
* cluster hosts running the same cron jobs, which is
* potentially worse.
*/
is_local = 0;
if (glue_strings(filename, sizeof filename, SPOOL_DIR, CRON_HOSTNAME, '/')) {
if ((f = fopen(filename, "r"))) {
if (EOF != get_string(hostname, MAXHOSTNAMELEN, f, "\n") &&
gethostname(myhostname, MAXHOSTNAMELEN) == 0) {
is_local = (strcmp(myhostname, hostname) == 0);
} else {
Debug(DLOAD, ("cluster: hostname comparison error\n"));
}
fclose(f);
} else {
Debug(DLOAD, ("cluster: file %s not found\n", filename));
}
}
return (is_local);
}
#if defined WITH_INOTIFY
void check_inotify_database(cron_db * old_db) {
cron_db new_db;
DIR_T *dp;
DIR *dir;
struct timeval tv;
fd_set rfds;
int retval;
char buf[BUF_LEN];
pid_t pid = getpid();
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(old_db->ifd, &rfds);
retval = select(old_db->ifd + 1, &rfds, NULL, NULL, &tv);
if (retval == -1) {
if (errno != EINTR)
log_it("CRON", pid, "INOTIFY", "select failed", errno);
return;
}
else if (FD_ISSET(old_db->ifd, &rfds)) {
new_db.head = new_db.tail = NULL;
new_db.ifd = old_db->ifd;
new_db.mtime = time(NULL) - 1;
while ((retval = (int)read(old_db->ifd, buf, sizeof (buf))) == -1 &&
errno == EINTR) ;
if (retval == 0) {
/* this should not happen as the buffer is large enough */
errno = ENOMEM;
}
if (retval <= 0) {
log_it("CRON", pid, "INOTIFY", "read failed", errno);
/* something fatal must have occurred we have no other reasonable
* way how to handle this failure than exit.
*/
(void) exit(ERROR_EXIT);
}
/* we must reinstate the watches here - TODO reinstate only watches
* which get IN_IGNORED event
*/
set_cron_watched(old_db->ifd);
/* TODO: parse the events and read only affected files */
#if defined ENABLE_SYSCRONTAB
process_crontab("root", NULL, SYSCRONTAB, &new_db, old_db);
#endif
if (!(dir = opendir(SYS_CROND_DIR))) {
log_it("CRON", pid, "OPENDIR FAILED", SYS_CROND_DIR, errno);
}
else {
while (NULL != (dp = readdir(dir))) {
char tabname[NAME_MAX + 1];
if (not_a_crontab(dp))
continue;
if (!glue_strings(tabname, sizeof tabname, SYS_CROND_DIR,
dp->d_name, '/'))
continue;
process_crontab("root", NULL, tabname, &new_db, old_db);
}
closedir(dir);
}
if (!(dir = opendir(SPOOL_DIR))) {
log_it("CRON", pid, "OPENDIR FAILED", SPOOL_DIR, errno);
}
else {
while (NULL != (dp = readdir(dir))) {
char fname[NAME_MAX + 1], tabname[NAME_MAX + 1];
if (not_a_crontab(dp))
continue;
strncpy(fname, dp->d_name, NAME_MAX + 1);
if (!glue_strings(tabname, sizeof tabname, SPOOL_DIR,
dp->d_name, '/'))
continue;
process_crontab(fname, fname, tabname, &new_db, old_db);
}
closedir(dir);
}
/* if we don't do this, then when our children eventually call
* getpwnam() in do_command.c's child_process to verify MAILTO=,
* they will screw us up (and v-v).
*/
endpwent();
}
else {
/* just return as no db reload is needed */
return;
}
overwrite_database(old_db, &new_db);
Debug(DLOAD, ("check_inotify_database is done\n"));
}
#endif
static void overwrite_database(cron_db * old_db, cron_db * new_db) {
user *u, *nu;
/* whatever's left in the old database is now junk.
*/
Debug(DLOAD, ("unlinking old database:\n"));
for (u = old_db->head; u != NULL; u = nu) {
Debug(DLOAD, ("\t%s\n", u->name));
nu = u->next;
unlink_user(old_db, u);
free_user(u);
}
/* overwrite the database control block with the new one.
*/
*old_db = *new_db;
}
int load_database(cron_db * old_db) {
struct stat statbuf, syscron_stat, crond_stat;
cron_db new_db;
DIR_T *dp;
DIR *dir;
pid_t pid = getpid();
int is_local = 0;
time_t now;
Debug(DLOAD, ("[%ld] load_database()\n", (long) pid));
now = time(NULL);
/* before we start loading any data, do a stat on SPOOL_DIR
* so that if anything changes as of this moment (i.e., before we've
* cached any of the database), we'll see the changes next time.
*/
if (stat(SPOOL_DIR, &statbuf) < OK) {
log_it("CRON", pid, "STAT FAILED", SPOOL_DIR, errno);
statbuf.st_mtime = 0;
}
else {
max_mtime(SPOOL_DIR, &statbuf);
}
if (stat(SYS_CROND_DIR, &crond_stat) < OK) {
log_it("CRON", pid, "STAT FAILED", SYS_CROND_DIR, errno);
crond_stat.st_mtime = 0;
}
else {
max_mtime(SYS_CROND_DIR, &crond_stat);
}
#if defined ENABLE_SYSCRONTAB
/* track system crontab file
*/
if (stat(SYSCRONTAB, &syscron_stat) < OK)
syscron_stat.st_mtime = 0;
#endif
/* if spooldir's mtime has not changed, we don't need to fiddle with
* the database.
*
* Note that old_db->mtime is initialized to 0 in main().
*
* We also use now - 1 as the upper bound of timestamp to avoid race,
* when a crontab is updated twice in a single second when we are
* just reading it.
*/
if (old_db->mtime != 0
&& old_db->mtime == TMIN(now - 1,
TMAX(crond_stat.st_mtime,
TMAX(statbuf.st_mtime, syscron_stat.st_mtime)))
) {
Debug(DLOAD, ("[%ld] spool dir mtime unch, no load needed.\n",
(long) pid));
return 0;
}
/* something's different. make a new database, moving unchanged
* elements from the old database, reloading elements that have
* actually changed. Whatever is left in the old database when
* we're done is chaff -- crontabs that disappeared.
*/
new_db.mtime = now - 1;
new_db.head = new_db.tail = NULL;
#if defined WITH_INOTIFY
new_db.ifd = old_db->ifd;
#endif
#if defined ENABLE_SYSCRONTAB
if (syscron_stat.st_mtime)
process_crontab("root", NULL, SYSCRONTAB, &new_db, old_db);
#endif
if (!(dir = opendir(SYS_CROND_DIR))) {
log_it("CRON", pid, "OPENDIR FAILED", SYS_CROND_DIR, errno);
}
else {
while (NULL != (dp = readdir(dir))) {
char tabname[NAME_MAX + 1];
if (not_a_crontab(dp))
continue;
if (!glue_strings(tabname, sizeof tabname, SYS_CROND_DIR,
dp->d_name, '/'))
continue; /* XXX log? */
process_crontab("root", NULL, tabname, &new_db, old_db);
}
closedir(dir);
}
/* we used to keep this dir open all the time, for the sake of
* efficiency. however, we need to close it in every fork, and
* we fork a lot more often than the mtime of the dir changes.
*/
if (!(dir = opendir(SPOOL_DIR))) {
log_it("CRON", pid, "OPENDIR FAILED", SPOOL_DIR, errno);
}
else {
is_local = cluster_host_is_local();
while (is_local && NULL != (dp = readdir(dir))) {
char fname[NAME_MAX + 1], tabname[NAME_MAX + 1];
if (not_a_crontab(dp))
continue;
strncpy(fname, dp->d_name, NAME_MAX);
fname[NAME_MAX] = '\0';
if (!glue_strings(tabname, sizeof tabname, SPOOL_DIR, fname, '/'))
continue; /* XXX log? */
process_crontab(fname, fname, tabname, &new_db, old_db);
}
closedir(dir);
}
/* if we don't do this, then when our children eventually call
* getpwnam() in do_command.c's child_process to verify MAILTO=,
* they will screw us up (and v-v).
*/
endpwent();
overwrite_database(old_db, &new_db);
Debug(DLOAD, ("load_database is done\n"));
return 1;
}
void link_user(cron_db * db, user * u) {
if (db->head == NULL)
db->head = u;
if (db->tail)
db->tail->next = u;
u->prev = db->tail;
u->next = NULL;
db->tail = u;
}
void unlink_user(cron_db * db, user * u) {
if (u->prev == NULL)
db->head = u->next;
else
u->prev->next = u->next;
if (u->next == NULL)
db->tail = u->prev;
else
u->next->prev = u->prev;
}
user *find_user(cron_db * db, const char *name, const char *tabname) {
user *u;
for (u = db->head; u != NULL; u = u->next)
if ((strcmp(u->name, name) == 0)
&& ((tabname == NULL)
|| (strcmp(tabname, u->tabname) == 0)
)
)
break;
return (u);
}
static int not_a_crontab(DIR_T * dp) {
size_t len;
/* avoid file names beginning with ".". this is good
* because we would otherwise waste two guaranteed calls
* to getpwnam() for . and .., and there shouldn't be
* hidden files in here anyway
*/
if (dp->d_name[0] == '.')
return (1);
/* ignore files starting with # and ending with ~ */
if (dp->d_name[0] == '#')
return (1);
/* ignore CRON_HOSTNAME file (in case doesn't start with ".") */
if (0 == strcmp(dp->d_name, CRON_HOSTNAME))
return(1);
len = strlen(dp->d_name);
if (len >= NAME_MAX || len == 0)
return (1);
if (dp->d_name[len - 1] == '~')
return (1);
if ((len > 8) && (strncmp(dp->d_name + len - 8, ".rpmsave", 8) == 0))
return (1);
if ((len > 8) && (strncmp(dp->d_name + len - 8, ".rpmorig", 8) == 0))
return (1);
if ((len > 7) && (strncmp(dp->d_name + len - 7, ".rpmnew", 7) == 0))
return (1);
return (0);
}
static void max_mtime(const char *dir_name, struct stat *max_st) {
DIR *dir;
DIR_T *dp;
struct stat st;
if (!(dir = opendir(dir_name))) {
max_st->st_mtime = 0;
return;
}
while (NULL != (dp = readdir(dir))) {
char tabname[NAME_MAX + 1];
if ( not_a_crontab ( dp ) && strcmp(dp->d_name, CRON_HOSTNAME) != 0)
continue;
if (!glue_strings(tabname, sizeof tabname, dir_name, dp->d_name, '/'))
continue; /* XXX log? */
if (stat(tabname, &st) < OK)
continue; /* XXX log? */
if (st.st_mtime > max_st->st_mtime)
max_st->st_mtime = st.st_mtime;
}
closedir(dir);
}

684
src/do_command.c Normal file
View File

@@ -0,0 +1,684 @@
/* Copyright 1988,1990,1993,1994 by Paul Vixie
* All rights reserved
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <ctype.h>
#include <errno.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <dirent.h>
#include "externs.h"
#include "funcs.h"
#include "globals.h"
#include "structs.h"
#include "cronie_common.h"
#ifndef isascii
# define isascii(c) ((unsigned)(c)<=0177)
#endif
static int child_process(entry *, char **);
static int safe_p(const char *, const char *);
void do_command(entry * e, user * u) {
pid_t pid = getpid();
int ev;
char **jobenv = NULL;
Debug(DPROC, ("[%ld] do_command(%s, (%s,%ld,%ld))\n",
(long) pid, e->cmd, u->name,
(long) e->pwd->pw_uid, (long) e->pwd->pw_gid));
/* fork to become asynchronous -- parent process is done immediately,
* and continues to run the normal cron code, which means return to
* tick(). the child and grandchild don't leave this function, alive.
*
* vfork() is unsuitable, since we have much to do, and the parent
* needs to be able to run off and fork other processes.
*/
switch (fork()) {
case -1:
log_it("CRON", pid, "CAN'T FORK", "do_command", errno);
break;
case 0:
/* child process */
acquire_daemonlock(1);
/* Set up the Red Hat security context for both mail/minder and job processes:
*/
if (cron_set_job_security_context(e, u, &jobenv) != 0) {
_exit(ERROR_EXIT);
}
ev = child_process(e, jobenv);
#ifdef WITH_PAM
cron_close_pam();
#endif
env_free(jobenv);
Debug(DPROC, ("[%ld] child process done, exiting\n", (long) getpid()));
_exit(ev);
break;
default:
/* parent process */
break;
}
Debug(DPROC, ("[%ld] main process returning to work\n", (long) pid));
}
static int child_process(entry * e, char **jobenv) {
int stdin_pipe[2], stdout_pipe[2];
char *input_data, *usernm, *mailto, *mailfrom;
char mailto_expanded[MAX_EMAILSTR];
char mailfrom_expanded[MAX_EMAILSTR];
int children = 0;
pid_t pid = getpid();
pid_t jobpid = -1;
struct sigaction sa;
/* Ignore SIGPIPE as we will be writing to pipes and do not want to terminate
prematurely */
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
/* our parent is watching for our death by catching SIGCHLD. we
* do not care to watch for our children's deaths this way -- we
* use wait() explicitly. so we have to reset the signal (which
* was inherited from the parent).
*/
sa.sa_handler = SIG_DFL;
sigaction(SIGCHLD, &sa, NULL);
Debug(DPROC, ("[%ld] child_process('%s')\n", (long) getpid(), e->cmd));
#ifdef CAPITALIZE_FOR_PS
/* mark ourselves as different to PS command watchers by upshifting
* our program name. This has no effect on some kernels.
*/
/*local */ {
char *pch;
for (pch = ProgramName; *pch; pch++)
*pch = (char)MkUpper(*pch);
}
#endif /* CAPITALIZE_FOR_PS */
/* discover some useful and important environment settings
*/
usernm = e->pwd->pw_name;
mailto = env_get("MAILTO", jobenv);
mailfrom = env_get("MAILFROM", e->envp);
if (mailto != NULL) {
if (expand_envvar(mailto, mailto_expanded, sizeof(mailto_expanded))) {
mailto = mailto_expanded;
}
else {
log_it("CRON", pid, "WARNING", "The environment variable 'MAILTO' could not be expanded. The non-expanded value will be used." , 0);
}
}
if (mailfrom != NULL) {
if (expand_envvar(mailfrom, mailfrom_expanded, sizeof(mailfrom_expanded))) {
mailfrom = mailfrom_expanded;
}
else {
log_it("CRON", pid, "WARNING", "The environment variable 'MAILFROM' could not be expanded. The non-expanded value will be used." , 0);
}
}
/* create some pipes to talk to our future child
*/
if (pipe(stdin_pipe) == -1) { /* child's stdin */
log_it("CRON", pid, "PIPE() FAILED", "stdin_pipe", errno);
return ERROR_EXIT;
}
if (pipe(stdout_pipe) == -1) { /* child's stdout */
log_it("CRON", pid, "PIPE() FAILED", "stdout_pipe", errno);
return ERROR_EXIT;
}
/* since we are a forked process, we can diddle the command string
* we were passed -- nobody else is going to use it again, right?
*
* if a % is present in the command, previous characters are the
* command, and subsequent characters are the additional input to
* the command. An escaped % will have the escape character stripped
* from it. Subsequent %'s will be transformed into newlines,
* but that happens later.
*/
/*local */ {
int escaped = FALSE;
int ch;
char *p;
for (input_data = p = e->cmd;
(ch = *input_data) != '\0'; input_data++, p++) {
if (p != input_data)
*p = (char)ch;
if (escaped) {
if (ch == '%')
*--p = (char)ch;
escaped = FALSE;
continue;
}
if (ch == '\\') {
escaped = TRUE;
continue;
}
if (ch == '%') {
*input_data++ = '\0';
break;
}
}
*p = '\0';
}
/* fork again, this time so we can exec the user's command.
*/
switch (jobpid = fork()) {
case -1:
log_it("CRON", pid, "CAN'T FORK", "child_process", errno);
return ERROR_EXIT;
/*NOTREACHED*/
case 0:
Debug(DPROC, ("[%ld] grandchild process fork()'ed\n", (long) getpid()));
/* write a log message. we've waited this long to do it
* because it was not until now that we knew the PID that
* the actual user command shell was going to get and the
* PID is part of the log message.
*/
if ((e->flags & DONT_LOG) == 0) {
char *x = mkprints((u_char *) e->cmd, strlen(e->cmd));
if (x == NULL) /* out of memory, better exit */
_exit(ERROR_EXIT);
log_it(usernm, getpid(), "CMD", x, 0);
free(x);
}
if (cron_change_user_permanently(e->pwd, env_get("HOME", jobenv)) < 0)
_exit(ERROR_EXIT);
/* get new pgrp, void tty, etc.
*/
(void) setsid();
/* reset the SIGPIPE back to default so the child will terminate
* if it tries to write to a closed pipe
*/
sa.sa_handler = SIG_DFL;
sigaction(SIGPIPE, &sa, NULL);
/* close the pipe ends that we won't use. this doesn't affect
* the parent, who has to read and write them; it keeps the
* kernel from recording us as a potential client TWICE --
* which would keep it from sending SIGPIPE in otherwise
* appropriate circumstances.
*/
close(stdin_pipe[WRITE_PIPE]);
close(stdout_pipe[READ_PIPE]);
/* grandchild process. make std{in,out} be the ends of
* pipes opened by our daddy; make stderr go to stdout.
*/
if (stdin_pipe[READ_PIPE] != STDIN) {
dup2(stdin_pipe[READ_PIPE], STDIN);
close(stdin_pipe[READ_PIPE]);
}
if (stdout_pipe[WRITE_PIPE] != STDOUT) {
dup2(stdout_pipe[WRITE_PIPE], STDOUT);
close(stdout_pipe[WRITE_PIPE]);
}
dup2(STDOUT, STDERR);
/*
* Exec the command.
*/
{
char *shell = env_get("SHELL", jobenv);
int fd, fdmax = TMIN(getdtablesize(), MAX_CLOSE_FD);
DIR *dir;
struct dirent *dent;
/*
* if /proc is mounted, we can optimize what fd can be closed,
* but if it isn't available, fall back to the previous behavior.
*/
if ((dir = opendir("/proc/self/fd")) != NULL) {
while ((dent = readdir(dir)) != NULL) {
if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
continue;
fd = atoi(dent->d_name);
if (fd > STDERR_FILENO)
close(fd);
}
} else {
/* close all unwanted open file descriptors */
for (fd = STDERR + 1; fd < fdmax; fd++) {
close(fd);
}
}
#if DEBUGGING
if (DebugFlags & DTEST) {
fprintf(stderr, "debug DTEST is on, not exec'ing command.\n");
fprintf(stderr, "\tcmd='%s' shell='%s'\n", e->cmd, shell);
_exit(OK_EXIT);
}
#endif /*DEBUGGING*/
execle(shell, shell, "-c", e->cmd, (char *) 0, jobenv);
fprintf(stderr, "execl: couldn't exec `%s'\n", shell);
perror("execl");
_exit(ERROR_EXIT);
}
break;
default:
cron_restore_default_security_context();
/* parent process */
break;
}
children++;
/* middle process, child of original cron, parent of process running
* the user's command.
*/
Debug(DPROC, ("[%ld] child continues, closing pipes\n", (long) getpid()));
/* close the ends of the pipe that will only be referenced in the
* grandchild process...
*/
close(stdin_pipe[READ_PIPE]);
close(stdout_pipe[WRITE_PIPE]);
/*
* write, to the pipe connected to child's stdin, any input specified
* after a % in the crontab entry. while we copy, convert any
* additional %'s to newlines. when done, if some characters were
* written and the last one wasn't a newline, write a newline.
*
* Note that if the input data won't fit into one pipe buffer (2K
* or 4K on most BSD systems), and the child doesn't read its stdin,
* we would block here. thus we must fork again.
*/
if (*input_data && fork() == 0) {
FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
int need_newline = FALSE;
int escaped = FALSE;
int ch;
Debug(DPROC, ("[%ld] child2 sending data to grandchild\n",
(long) getpid()));
/* reset the SIGPIPE back to default so the child will terminate
* if it tries to write to a closed pipe
*/
sa.sa_handler = SIG_DFL;
sigaction(SIGPIPE, &sa, NULL);
/* close the pipe we don't use, since we inherited it and
* are part of its reference count now.
*/
close(stdout_pipe[READ_PIPE]);
if (cron_change_user_permanently(e->pwd, env_get("HOME", jobenv)) < 0)
_exit(ERROR_EXIT);
/* translation:
* \% -> %
* % -> \n
* \x -> \x for all x != %
*/
while ((ch = *input_data++) != '\0') {
if (escaped) {
if (ch != '%')
putc('\\', out);
}
else {
if (ch == '%')
ch = '\n';
}
if (!(escaped = (ch == '\\'))) {
putc(ch, out);
need_newline = (ch != '\n');
}
}
if (escaped)
putc('\\', out);
if (need_newline)
putc('\n', out);
/* close the pipe, causing an EOF condition. fclose causes
* stdin_pipe[WRITE_PIPE] to be closed, too.
*/
fclose(out);
Debug(DPROC, ("[%ld] child2 done sending to grandchild\n",
(long) getpid()));
_exit(0);
}
/* close the pipe to the grandkiddie's stdin, since its wicked uncle
* ernie back there has it open and will close it when he's done.
*/
close(stdin_pipe[WRITE_PIPE]);
children++;
/*
* read output from the grandchild. it's stderr has been redirected to
* it's stdout, which has been redirected to our pipe. if there is any
* output, we'll be mailing it to the user whose crontab this is...
* when the grandchild exits, we'll get EOF.
*/
Debug(DPROC, ("[%ld] child reading output from grandchild\n",
(long) getpid()));
/*local */ {
FILE *in = fdopen(stdout_pipe[READ_PIPE], "r");
int ch = getc(in);
if (ch != EOF) {
FILE *mail = NULL;
int bytes = 1;
int status = 0;
#if defined(SYSLOG)
char logbuf[1024];
int bufidx = 0;
if (SyslogOutput) {
if (ch != '\n')
logbuf[bufidx++] = (char)ch;
}
#endif
Debug(DPROC | DEXT,
("[%ld] got data (%x:%c) from grandchild\n",
(long) getpid(), ch, ch));
/* get name of recipient. this is MAILTO if set to a
* valid local username; USER otherwise.
*/
if (mailto) {
/* MAILTO was present in the environment
*/
if (!*mailto) {
/* ... but it's empty. set to NULL
*/
mailto = NULL;
}
}
else {
/* MAILTO not present, set to USER.
*/
mailto = usernm;
}
/* get sender address. this is MAILFROM if set (and safe),
* the user account name otherwise.
*/
if (!mailfrom || !*mailfrom || !safe_p(usernm, mailfrom)) {
mailfrom = e->pwd->pw_name;
}
/* if we are supposed to be mailing, MAILTO will
* be non-NULL. only in this case should we set
* up the mail command and subjects and stuff...
*/
/* Also skip it if MailCmd is set to "off" */
if (mailto && safe_p(usernm, mailto)
&& strncmp(MailCmd,"off",3) && !SyslogOutput) {
char **env;
char mailcmd[MAX_COMMAND+1]; /* +1 for terminator */
char hostname[MAXHOSTNAMELEN];
char *content_type = env_get("CONTENT_TYPE", jobenv),
*content_transfer_encoding =
env_get("CONTENT_TRANSFER_ENCODING", jobenv);
gethostname(hostname, MAXHOSTNAMELEN);
if (MailCmd[0] == '\0') {
int len;
len = snprintf(mailcmd, sizeof mailcmd, MAILFMT, MAILARG, mailfrom);
if (len < 0) {
fprintf(stderr, "mailcmd snprintf failed\n");
(void) _exit(ERROR_EXIT);
}
if (sizeof mailcmd <= (size_t) len) {
fprintf(stderr, "mailcmd too long\n");
(void) _exit(ERROR_EXIT);
}
}
else {
strncpy(mailcmd, MailCmd, MAX_COMMAND+1);
}
if (!(mail = cron_popen(mailcmd, "w", e->pwd, jobenv))) {
perror(mailcmd);
(void) _exit(ERROR_EXIT);
}
fprintf(mail, "From: \"(Cron Daemon)\" <%s>\n", mailfrom);
fprintf(mail, "To: %s\n", mailto);
fprintf(mail, "Subject: Cron <%s@%s> %s\n",
usernm, first_word(hostname, "."), e->cmd);
#ifdef MAIL_DATE
fprintf(mail, "Date: %s\n", arpadate(&StartTime));
#endif /*MAIL_DATE */
fprintf(mail, "MIME-Version: 1.0\n");
if (content_type == NULL) {
fprintf(mail, "Content-Type: text/plain; charset=%s\n",
cron_default_mail_charset);
}
else { /* user specified Content-Type header.
* disallow new-lines for security reasons
* (else users could specify arbitrary mail headers!)
*/
char *nl = content_type;
size_t ctlen = strlen(content_type);
while ((*nl != '\0')
&& ((nl = strchr(nl, '\n')) != NULL)
&& (nl < (content_type + ctlen))
)
*nl = ' ';
fprintf(mail, "Content-Type: %s\n", content_type);
}
if (content_transfer_encoding == NULL) {
fprintf(mail, "Content-Transfer-Encoding: 8bit\n");
}
else {
char *nl = content_transfer_encoding;
size_t ctlen = strlen(content_transfer_encoding);
while ((*nl != '\0')
&& ((nl = strchr(nl, '\n')) != NULL)
&& (nl < (content_transfer_encoding + ctlen))
)
*nl = ' ';
fprintf(mail, "Content-Transfer-Encoding: %s\n",
content_transfer_encoding);
}
/* The Auto-Submitted header is
* defined (and suggested by) RFC3834.
*/
fprintf(mail, "Auto-Submitted: auto-generated\n");
fprintf(mail, "Precedence: bulk\n");
for (env = jobenv; *env; env++)
fprintf(mail, "X-Cron-Env: <%s>\n", *env);
fprintf(mail, "\n");
/* this was the first char from the pipe
*/
putc(ch, mail);
}
/* we have to read the input pipe no matter whether
* we mail or not, but obviously we only write to
* mail pipe if we ARE mailing.
*/
while (EOF != (ch = getc(in))) {
if (ch == '\r')
continue;
bytes++;
if (mail)
putc(ch, mail);
#if defined(SYSLOG)
if (SyslogOutput) {
logbuf[bufidx++] = (char)ch;
if ((ch == '\n') || (bufidx == sizeof(logbuf)-1)) {
if (ch == '\n')
logbuf[bufidx-1] = '\0';
else
logbuf[bufidx] = '\0';
log_it(usernm, getpid(), "CMDOUT", logbuf, 0);
bufidx = 0;
}
}
#endif
}
/* if -n option was specified, abort the sending
* now when we read all of the command output
* and thus can wait for it's exit status
*/
if (mail && e->flags & MAIL_WHEN_ERR) {
int jobstatus = -1;
if (jobpid > 0) {
while (waitpid(jobpid, &jobstatus, 0) == -1) {
if (errno == EINTR) continue;
log_it("CRON", getpid(), "error", "invalid job pid", errno);
break;
}
} else {
log_it("CRON", getpid(), "error", "invalid job pid", 0);
}
/* if everything went well, -n is set, and we have mail,
* we won't be mailing so shoot the messenger!
*/
if (WIFEXITED(jobstatus) && WEXITSTATUS(jobstatus) == EXIT_SUCCESS) {
Debug(DPROC, ("[%ld] aborting pipe to mail\n", (long)getpid()));
status = cron_pabort(mail);
mail = NULL;
}
}
/* only close pipe if we opened it -- i.e., we're (still)
* mailing...
*/
if (mail) {
Debug(DPROC, ("[%ld] closing pipe to mail\n", (long) getpid()));
/* Note: the pclose will probably see
* the termination of the grandchild
* in addition to the mail process, since
* it (the grandchild) is likely to exit
* after closing its stdout.
*/
status = cron_pclose(mail);
}
#if defined(SYSLOG)
if (SyslogOutput) {
if (bufidx) {
logbuf[bufidx] = '\0';
log_it(usernm, getpid(), "CMDOUT", logbuf, 0);
}
}
#endif
/* if there was output and we could not mail it,
* log the facts so the poor user can figure out
* what's going on.
*/
if (mail && status && !SyslogOutput) {
char buf[MAX_TEMPSTR];
sprintf(buf,
"mailed %d byte%s of output but got status 0x%04x\n",
bytes, (bytes == 1) ? "" : "s", status);
log_it(usernm, getpid(), "MAIL", buf, 0);
}
} /*if data from grandchild */
Debug(DPROC, ("[%ld] got EOF from grandchild\n", (long) getpid()));
fclose(in); /* also closes stdout_pipe[READ_PIPE] */
}
/* wait for children to die.
*/
for (; children > 0; children--) {
WAIT_T waiter;
PID_T child;
Debug(DPROC, ("[%ld] waiting for grandchild #%d to finish\n",
(long) getpid(), children));
while ((child = wait(&waiter)) < OK && errno == EINTR) ;
if (child < OK) {
Debug(DPROC,
("[%ld] no more grandchildren--mail written?\n",
(long) getpid()));
break;
}
Debug(DPROC, ("[%ld] grandchild #%ld finished, status=%04x",
(long) getpid(), (long) child, WEXITSTATUS(waiter)));
if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
Debug(DPROC, (", dumped core"));
Debug(DPROC, ("\n"));
}
if ((e->flags & DONT_LOG) == 0) {
char *x = mkprints((u_char *) e->cmd, strlen(e->cmd));
log_it(usernm, getpid(), "CMDEND", x ? x : "**Unknown command**" , 0);
free(x);
}
return OK_EXIT;
}
static int safe_p(const char *usernm, const char *s) {
static const char safe_delim[] = "@!:%-.,_+"; /* conservative! */
const char *t;
int ch, first;
for (t = s, first = 1; (ch = *t++) != '\0'; first = 0) {
if (isascii(ch) && isprint(ch) &&
(isalnum(ch) || (!first && strchr(safe_delim, ch))))
continue;
log_it(usernm, getpid(), "UNSAFE", s, 0);
return (FALSE);
}
return (TRUE);
}

748
src/entry.c Normal file
View File

@@ -0,0 +1,748 @@
/*
* Copyright 1988,1990,1993,1994 by Paul Vixie
* All rights reserved
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* vix 26jan87 [RCS'd; rest of log is in RCS file]
* vix 01jan87 [added line-level error recovery]
* vix 31dec86 [added /step to the from-to range, per bob@acornrc]
* vix 30dec86 [written]
*/
#include "config.h"
#include <ctype.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "bitstring.h"
#include "funcs.h"
#include "globals.h"
#include "macros.h"
#include "pathnames.h"
typedef enum ecode {
e_none, e_minute, e_hour, e_dom, e_month, e_dow,
e_cmd, e_timespec, e_username, e_option, e_memory
} ecode_e;
static const char *ecodes[] = {
"no error",
"bad minute",
"bad hour",
"bad day-of-month",
"bad month",
"bad day-of-week",
"bad command",
"bad time specifier",
"bad username",
"bad option",
"out of memory"
};
typedef enum {
R_START,
R_AST,
R_STEP,
R_TERMS,
R_NUM1,
R_RANGE,
R_RANGE_NUM2,
R_RANDOM,
R_RANDOM_NUM2,
R_FINISH,
} range_state_t;
static int get_list(bitstr_t *, int, int, const char *[], int, FILE *),
get_range(bitstr_t *, int, int, const char *[], FILE *),
get_number(int *, int, const char *[], FILE *),
set_element(bitstr_t *, int, int, int);
void free_entry(entry * e) {
free(e->cmd);
free(e->pwd);
env_free(e->envp);
free(e);
}
/* return NULL if eof or syntax error occurs;
* otherwise return a pointer to a new entry.
*/
entry *load_entry(FILE * file, void (*error_func) (), struct passwd *pw,
char **envp) {
/* this function reads one crontab entry -- the next -- from a file.
* it skips any leading blank lines, ignores comments, and returns
* NULL if for any reason the entry can't be read and parsed.
*
* the entry is also parsed here.
*
* syntax:
* user crontab:
* minutes hours doms months dows cmd\n
* system crontab (/etc/crontab):
* minutes hours doms months dows USERNAME cmd\n
*/
ecode_e ecode = e_none;
entry *e = NULL;
int ch;
char cmd[MAX_COMMAND];
char envstr[MAX_ENVSTR];
char **tenvp;
char *p;
struct passwd temppw;
int i;
Debug(DPARS, ("load_entry()...about to eat comments\n"));
ch = get_char(file);
if (ch == EOF)
return (NULL);
/* ch is now the first useful character of a useful line.
* it may be an @special or it may be the first character
* of a list of minutes.
*/
e = (entry *) calloc(sizeof (entry), sizeof (char));
if (e == NULL) {
ecode = e_memory;
goto eof;
}
/* check for '-' as a first character, this option will disable
* writing a syslog message about command getting executed
*/
if (ch == '-') {
/* if we are editing system crontab or user uid is 0 (root)
* we are allowed to disable logging
*/
if (pw == NULL || pw->pw_uid == 0)
e->flags |= DONT_LOG;
else {
log_it("CRON", getpid(), "ERROR", "Only privileged user can disable logging", 0);
ecode = e_option;
goto eof;
}
ch = get_char(file);
if (ch == EOF) {
free(e);
return NULL;
}
}
if (ch == '@') {
/* all of these should be flagged and load-limited; i.e.,
* instead of @hourly meaning "0 * * * *" it should mean
* "close to the front of every hour but not 'til the
* system load is low". Problems are: how do you know
* what "low" means? (save me from /etc/cron.conf!) and:
* how to guarantee low variance (how low is low?), which
* means how to we run roughly every hour -- seems like
* we need to keep a history or let the first hour set
* the schedule, which means we aren't load-limited
* anymore. too much for my overloaded brain. (vix, jan90)
* HINT
*/
ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
if (!strcmp("reboot", cmd)) {
e->flags |= WHEN_REBOOT;
}
else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)) {
bit_set(e->minute, 0);
bit_set(e->hour, 0);
bit_set(e->dom, 0);
bit_set(e->month, 0);
bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW);
e->flags |= DOW_STAR;
}
else if (!strcmp("monthly", cmd)) {
bit_set(e->minute, 0);
bit_set(e->hour, 0);
bit_set(e->dom, 0);
bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW);
e->flags |= DOW_STAR;
}
else if (!strcmp("weekly", cmd)) {
bit_set(e->minute, 0);
bit_set(e->hour, 0);
bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM);
bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
bit_set(e->dow, 0);
e->flags |= DOM_STAR;
}
else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
bit_set(e->minute, 0);
bit_set(e->hour, 0);
bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM);
bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW);
}
else if (!strcmp("hourly", cmd)) {
bit_set(e->minute, 0);
bit_nset(e->hour, 0, LAST_HOUR - FIRST_HOUR);
bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM);
bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW);
e->flags |= HR_STAR;
}
else {
ecode = e_timespec;
goto eof;
}
/* Advance past whitespace between shortcut and
* username/command.
*/
Skip_Blanks(ch, file);
if (ch == EOF || ch == '\n') {
ecode = e_cmd;
goto eof;
}
}
else {
Debug(DPARS, ("load_entry()...about to parse numerics\n"));
if (ch == '*')
e->flags |= MIN_STAR;
ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, PPC_NULL, ch, file);
if (ch == EOF) {
ecode = e_minute;
goto eof;
}
/* hours
*/
if (ch == '*')
e->flags |= HR_STAR;
ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, PPC_NULL, ch, file);
if (ch == EOF) {
ecode = e_hour;
goto eof;
}
/* DOM (days of month)
*/
if (ch == '*')
e->flags |= DOM_STAR;
ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file);
if (ch == EOF) {
ecode = e_dom;
goto eof;
}
/* month
*/
ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, MonthNames, ch, file);
if (ch == EOF) {
ecode = e_month;
goto eof;
}
/* DOW (days of week)
*/
if (ch == '*')
e->flags |= DOW_STAR;
ch = get_list(e->dow, FIRST_DOW, LAST_DOW, DowNames, ch, file);
if (ch == EOF) {
ecode = e_dow;
goto eof;
}
}
/* make sundays equivalent */
if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
bit_set(e->dow, 0);
bit_set(e->dow, 7);
}
/* check for permature EOL and catch a common typo */
if (ch == '\n' || ch == '*') {
ecode = e_cmd;
goto eof;
}
/* ch is the first character of a command, or a username */
unget_char(ch, file);
if (!pw) {
char *username = cmd; /* temp buffer */
Debug(DPARS, ("load_entry()...about to parse username\n"));
ch = get_string(username, MAX_COMMAND, file, " \t\n");
Debug(DPARS, ("load_entry()...got %s\n", username));
if (ch == EOF || ch == '\n' || ch == '*') {
ecode = e_cmd;
goto eof;
}
pw = getpwnam(username);
if (pw == NULL) {
Debug(DPARS, ("load_entry()...unknown user entry\n"));
memset(&temppw, 0, sizeof (temppw));
temppw.pw_name = username;
temppw.pw_passwd = "";
pw = &temppw;
} else {
Debug(DPARS, ("load_entry()...uid %ld, gid %ld\n",
(long) pw->pw_uid, (long) pw->pw_gid));
}
/* Advance past whitespace before command. */
Skip_Blanks(ch, file);
/* check for permature EOL or EOF */
if (ch == EOF || ch == '\n') {
ecode = e_cmd;
goto eof;
}
/* ch is the first character of a command */
unget_char(ch, file);
}
if ((e->pwd = pw_dup(pw)) == NULL) {
ecode = e_memory;
goto eof;
}
memset(e->pwd->pw_passwd, 0, strlen(e->pwd->pw_passwd));
p = env_get("RANDOM_DELAY", envp);
if (p) {
char *endptr;
long val;
errno = 0; /* To distinguish success/failure after call */
val = strtol(p, &endptr, 10);
if (errno != 0 || val < 0 || val > 24*60) {
log_it("CRON", getpid(), "ERROR", "bad value of RANDOM_DELAY", 0);
} else {
e->delay = (int)((double)val * RandomScale);
}
}
/* copy and fix up environment. some variables are just defaults and
* others are overrides.
*/
if ((e->envp = env_copy(envp)) == NULL) {
ecode = e_memory;
goto eof;
}
if (!env_get("SHELL", e->envp)) {
if (glue_strings(envstr, sizeof envstr, "SHELL", _PATH_BSHELL, '=')) {
if ((tenvp = env_set(e->envp, envstr)) == NULL) {
ecode = e_memory;
goto eof;
}
e->envp = tenvp;
}
else
log_it("CRON", getpid(), "ERROR", "can't set SHELL", 0);
}
if ((tenvp = env_update_home(e->envp, pw->pw_dir)) == NULL) {
ecode = e_memory;
goto eof;
}
e->envp = tenvp;
#ifndef LOGIN_CAP
/* If login.conf is in used we will get the default PATH later. */
if (!env_get("PATH", e->envp)) {
char *defpath;
if (ChangePath)
defpath = _PATH_STDPATH;
else {
defpath = getenv("PATH");
if (defpath == NULL)
defpath = _PATH_STDPATH;
}
if (glue_strings(envstr, sizeof envstr, "PATH", defpath, '=')) {
if ((tenvp = env_set(e->envp, envstr)) == NULL) {
ecode = e_memory;
goto eof;
}
e->envp = tenvp;
}
else
log_it("CRON", getpid(), "ERROR", "can't set PATH", 0);
}
#endif /* LOGIN_CAP */
if (glue_strings(envstr, sizeof envstr, "LOGNAME", pw->pw_name, '=')) {
if ((tenvp = env_set(e->envp, envstr)) == NULL) {
ecode = e_memory;
goto eof;
}
e->envp = tenvp;
}
else
log_it("CRON", getpid(), "ERROR", "can't set LOGNAME", 0);
#if defined(BSD) || defined(__linux)
if (glue_strings(envstr, sizeof envstr, "USER", pw->pw_name, '=')) {
if ((tenvp = env_set(e->envp, envstr)) == NULL) {
ecode = e_memory;
goto eof;
}
e->envp = tenvp;
}
else
log_it("CRON", getpid(), "ERROR", "can't set USER", 0);
#endif
Debug(DPARS, ("load_entry()...about to parse command\n"));
/* If the first character of the command is '-', it is a cron option. */
ch = get_char(file);
while (ch == '-') {
switch (ch = get_char(file)) {
case 'n':
/* only allow user to set the option once */
if ((e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR) {
ecode = e_option;
goto eof;
}
e->flags |= MAIL_WHEN_ERR;
break;
default:
ecode = e_option;
goto eof;
}
ch = get_char(file);
if (ch != '\t' && ch != ' ') {
ecode = e_option;
goto eof;
}
Skip_Blanks(ch, file);
if (ch == EOF || ch == '\n') {
ecode = e_cmd;
goto eof;
}
}
unget_char(ch, file);
/* Everything up to the next \n or EOF is part of the command...
* too bad we don't know in advance how long it will be, since we
* need to malloc a string for it... so, we limit it to MAX_COMMAND.
*/
ch = get_string(cmd, MAX_COMMAND, file, "\n");
/* a file without a \n before the EOF is rude, so we'll complain...
*/
if (ch == EOF) {
ecode = e_cmd;
goto eof;
}
/* got the command in the 'cmd' string; save it in *e.
*/
if ((e->cmd = strdup(cmd)) == NULL) {
ecode = e_memory;
goto eof;
}
Debug(DPARS, ("load_entry()...returning successfully\n"));
/* success, fini, return pointer to the entry we just created...
*/
return (e);
eof:
if (e) {
if (e->envp)
env_free(e->envp);
free(e->pwd);
free(e->cmd);
free(e);
}
for (i = 0; i < MAX_COMMAND && ch != '\n' && !feof(file); i++)
ch = get_char(file);
if (ecode != e_none && error_func)
(*error_func) (ecodes[(int) ecode]);
return (NULL);
}
static int
get_list(bitstr_t * bits, int low, int high, const char *names[],
int ch, FILE * file) {
int done;
/* we know that we point to a non-blank character here;
* must do a Skip_Blanks before we exit, so that the
* next call (or the code that picks up the cmd) can
* assume the same thing.
*/
Debug(DPARS | DEXT, ("get_list()...entered\n"));
/* list = range {"," range}
*/
/* clear the bit string, since the default is 'off'.
*/
bit_nclear(bits, 0, (high - low));
/* process all ranges
*/
done = FALSE;
/* unget ch to allow get_range() to process it properly
*/
unget_char(ch, file);
while (!done) {
if (EOF == (ch = get_range(bits, low, high, names, file)))
return (EOF);
if (ch == ',')
continue;
else
done = TRUE;
}
/* exiting. skip to some blanks, then skip over the blanks.
*/
Skip_Nonblanks(ch, file)
Skip_Blanks(ch, file)
Debug(DPARS | DEXT, ("get_list()...exiting w/ %02x\n", ch));
return (ch);
}
inline static int is_separator(int ch) {
switch (ch) {
case '\t':
case '\n':
case ' ':
case ',':
return 1;
default:
return 0;
}
}
static int
get_range(bitstr_t * bits, int low, int high, const char *names[],
FILE * file) {
/* range = number | number "-" number [ "/" number ]
* | [number] "~" [number]
*/
int ch, i, low_, high_, step;
/* default value for step
*/
step = 1;
range_state_t state = R_START;
while (state != R_FINISH && ((ch = get_char(file)) != EOF)) {
switch (state) {
case R_START:
if (ch == '*') {
low_ = low;
high_ = high;
state = R_AST;
break;
}
if (ch == '~') {
low_ = low;
state = R_RANDOM;
break;
}
unget_char(ch, file);
if (get_number(&low_, low, names, file) != EOF) {
state = R_NUM1;
break;
}
return (EOF);
case R_AST:
if (ch == '/') {
state = R_STEP;
break;
}
if (is_separator(ch)) {
state = R_FINISH;
break;
}
return (EOF);
case R_STEP:
unget_char(ch, file);
if (get_number(&step, 0, PPC_NULL, file) != EOF
&& step != 0) {
state = R_TERMS;
break;
}
return (EOF);
case R_TERMS:
if (is_separator(ch)) {
state = R_FINISH;
break;
}
return (EOF);
case R_NUM1:
if (ch == '-') {
state = R_RANGE;
break;
}
if (ch == '~') {
state = R_RANDOM;
break;
}
if (is_separator(ch)) {
high_ = low_;
state = R_FINISH;
break;
}
return (EOF);
case R_RANGE:
unget_char(ch, file);
if (get_number(&high_, low, names, file) != EOF) {
state = R_RANGE_NUM2;
break;
}
return (EOF);
case R_RANGE_NUM2:
if (ch == '/') {
state = R_STEP;
break;
}
if (is_separator(ch)) {
state = R_FINISH;
break;
}
return (EOF);
case R_RANDOM:
if (is_separator(ch)) {
high_ = high;
state = R_FINISH;
}
else if (unget_char(ch, file),
get_number(&high_, low, names, file) != EOF) {
state = R_TERMS;
}
/* fail if couldn't find match on previous term
*/
else
return (EOF);
/* if invalid random range was selected */
if (low_ > high_)
return (EOF);
/* select random number in range <low_, high_>
*/
low_ = high_ = random() % (high_ - low_ + 1) + low_;
break;
default:
/* We should never get here
*/
return (EOF);
}
}
if (state != R_FINISH || ch == EOF)
return (EOF);
/* Make sure the step size makes any sense */
if (step > 1 && step > (high_ - low_)) {
int max = high_ - low_ > 0 ? high_ - low_ : 1;
fprintf(stderr, "Warning: Step size %i higher than possible maximum of %i\n", step, max);
}
for (i = low_; i <= high_; i += step)
if (EOF == set_element(bits, low, high, i)) {
unget_char(ch, file);
return (EOF);
}
return ch;
}
static int
get_number(int *numptr, int low, const char *names[], FILE * file) {
char temp[MAX_TEMPSTR], *pc;
int len, i, ch;
char *endptr;
pc = temp;
len = 0;
/* get all alnum characters available */
while (isalnum((ch = get_char(file)))) {
if (++len >= MAX_TEMPSTR)
goto bad;
*pc++ = (char)ch;
}
*pc = '\0';
if (len == 0)
goto bad;
unget_char(ch, file);
/* try to get number */
*numptr = (int) strtol(temp, &endptr, 10);
if (*endptr == '\0' && temp != endptr) {
/* We have a number */
return 0;
}
/* no numbers, look for a string if we have any */
if (names) {
for (i = 0; names[i] != NULL; i++) {
Debug(DPARS | DEXT, ("get_num, compare(%s,%s)\n", names[i], temp));
if (strcasecmp(names[i], temp) == 0) {
*numptr = i + low;
return 0;
}
}
} else {
goto bad;
}
bad:
unget_char(ch, file);
return (EOF);
}
static int set_element(bitstr_t * bits, int low, int high, int number) {
Debug(DPARS | DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number));
if (number < low || number > high)
return (EOF);
bit_set(bits, (number - low));
return (OK);
}

306
src/env.c Normal file
View File

@@ -0,0 +1,306 @@
/* Copyright 1988,1990,1993,1994 by Paul Vixie
* All rights reserved
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <ctype.h>
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "globals.h"
#include "funcs.h"
#if defined(BSD)
extern char **environ;
#endif
char **env_init(void) {
char **p = (char **) malloc(sizeof (char *));
if (p != NULL)
p[0] = NULL;
return (p);
}
void env_free(char **envp) {
char **p;
for (p = envp; *p != NULL; p++)
free(*p);
free(envp);
}
char **env_copy(char **envp) {
int save_errno;
size_t count, i;
char **p;
for (count = 0; envp[count] != NULL; count++) ;
p = (char **) malloc((count + 1) * sizeof (char *)); /* 1 for the NULL */
if (p != NULL) {
for (i = 0; i < count; i++)
if ((p[i] = strdup(envp[i])) == NULL) {
save_errno = errno;
while (i-- > 0)
free(p[i]);
free(p);
errno = save_errno;
return (NULL);
}
p[count] = NULL;
}
return (p);
}
char **env_set(char **envp, const char *envstr) {
size_t count, found;
char **p, *envtmp;
/*
* count the number of elements, including the null pointer;
* also set 'found' to -1 or index of entry if already in here.
*/
found = (size_t)-1;
for (count = 0; envp[count] != NULL; count++) {
if (!strcmp_until(envp[count], envstr, '='))
found = count;
}
count++; /* for the NULL */
if (found != (size_t)-1) {
/*
* it exists already, so just free the existing setting,
* save our new one there, and return the existing array.
*/
if ((envtmp = strdup(envstr)) == NULL)
return (NULL);
free(envp[found]);
envp[found] = envtmp;
return (envp);
}
/*
* it doesn't exist yet, so resize the array, move null pointer over
* one, save our string over the old null pointer, and return resized
* array.
*/
if ((envtmp = strdup(envstr)) == NULL)
return (NULL);
p = (char **) realloc((void *) envp,
(count + 1) * sizeof (char *));
if (p == NULL) {
free(envtmp);
return (NULL);
}
p[count] = p[count - 1];
p[count - 1] = envtmp;
return (p);
}
int env_set_from_environ(char ***envpp) {
static const char *names[] = {
"LANG",
"LC_CTYPE",
"LC_NUMERIC",
"LC_TIME",
"LC_COLLATE",
"LC_MONETARY",
"LC_MESSAGES",
"LC_PAPER",
"LC_NAME",
"LC_ADDRESS",
"LC_TELEPHONE",
"LC_MEASUREMENT",
"LC_IDENTIFICATION",
"LC_ALL",
"LANGUAGE",
"RANDOM_DELAY",
"MAILFROM",
NULL
};
const char **name;
char **procenv;
for (procenv = environ; *procenv != NULL; ++procenv) {
for (name = names; *name != NULL; ++name) {
size_t namelen;
namelen = strlen(*name);
if (strncmp(*name, *procenv, namelen) == 0
&& (*procenv)[namelen] == '=') {
char **tmpenv;
tmpenv = env_set(*envpp, *procenv);
if (tmpenv == NULL)
return FALSE;
*envpp = tmpenv;
}
}
}
return TRUE;
}
/* The following states are used by load_env(), traversed in order: */
enum env_state {
NAMEI, /* First char of NAME, may be quote */
NAME, /* Subsequent chars of NAME */
EQ1, /* After end of name, looking for '=' sign */
EQ2, /* After '=', skipping whitespace */
VALUEI, /* First char of VALUE, may be quote */
VALUE, /* Subsequent chars of VALUE */
FINI, /* All done, skipping trailing whitespace */
ERROR, /* Error */
};
/* return ERR = end of file
* FALSE = not an env setting (file was repositioned)
* TRUE = was an env setting
*/
int load_env(char *envstr, FILE * f) {
long filepos;
int fileline;
enum env_state state;
char quotechar, *c, *str, *val;
filepos = ftell(f);
fileline = LineNumber;
if (EOF == get_string(envstr, MAX_ENVSTR, f, "\n"))
return (ERR);
Debug(DPARS, ("load_env, read <%s>\n", envstr));
val = str = envstr;
state = NAMEI;
quotechar = '\0';
c = envstr;
while (state != ERROR && *c) {
switch (state) {
case NAMEI:
case VALUEI:
if (*c == '\'' || *c == '"')
quotechar = *c++;
state++;
/* FALLTHROUGH */
case NAME:
case VALUE:
if (quotechar) {
if (*c == quotechar) {
state++;
c++;
break;
}
if (state == NAME && *c == '=') {
state = ERROR;
break;
}
}
else {
if (state == NAME) {
if (isspace((unsigned char) *c)) {
c++;
state++;
break;
}
if (*c == '=') {
state++;
break;
}
}
}
*str++ = *c++;
break;
case EQ1:
if (*c == '=') {
state++;
quotechar = '\0';
*str++ = *c;
val = str;
}
else {
if (!isspace((unsigned char) *c))
state = ERROR;
}
c++;
break;
case EQ2:
case FINI:
if (isspace((unsigned char) *c))
c++;
else
state++;
break;
default:
abort();
}
}
if (state != FINI && state != EQ2 && !(state == VALUE && !quotechar)) {
Debug(DPARS, ("load_env, not an env var, state = %d\n", state));
if (fseek(f, filepos, 0)) {
return ERR;
}
Set_LineNum(fileline);
return (FALSE);
}
*str = '\0';
if (state == VALUE) {
/* End of unquoted value: trim trailing whitespace */
while (str > val && isspace((unsigned char)str[-1]))
*(--str) = '\0';
}
return TRUE;
}
char *env_get(const char *name, char **envp) {
size_t len = strlen(name);
char *p, *q;
while ((p = *envp++) != NULL) {
if (!(q = strchr(p, '=')))
continue;
if ((size_t)(q - p) == len && !strncmp(p, name, len))
return (q + 1);
}
return (NULL);
}
char **env_update_home(char **envp, const char *dir) {
char envstr[MAX_ENVSTR];
if (dir == NULL || *dir == '\0' || env_get("HOME", envp)) {
return envp;
}
if (glue_strings(envstr, sizeof envstr, "HOME", dir, '=')) {
envp = env_set(envp, envstr);
}
else
log_it("CRON", getpid(), "ERROR", "can't set HOME", 0);
return envp;
}

91
src/externs.h Normal file
View File

@@ -0,0 +1,91 @@
/* Copyright 1993,1994 by Paul Vixie
* All rights reserved
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* reorder these #include's at your peril */
#ifndef CRONIE_EXTERNS_H
#define CRONIE_EXTERNS_H
#if defined(LOGIN_CAP)
# include <login_cap.h>
#endif /*LOGIN_CAP*/
#if defined(BSD_AUTH)
# include <bsd_auth.h>
#endif /*BSD_AUTH*/
#define DIR_T struct dirent
#define WAIT_T int
#define SIG_T sig_t
#define TIME_T time_t
#define PID_T pid_t
#ifndef TZNAME_ALREADY_DEFINED
extern char *tzname[2];
#endif
#define TZONE(tm) tzname[(tm).tm_isdst]
#if (defined(BSD)) && (BSD >= 199103) || defined(__linux) || defined(__sun) || defined(_AIX)
# define HAVE_SAVED_UIDS
#endif
#define MY_UID(pw) getuid()
#define MY_GID(pw) getgid()
/* getopt() isn't part of POSIX. some systems define it in <stdlib.h> anyway.
* of those that do, some complain that our definition is different and some
* do not. to add to the misery and confusion, some systems define getopt()
* in ways that we cannot predict or comprehend, yet do not define the adjunct
* external variables needed for the interface.
*/
#if (!defined(BSD) || (BSD < 198911))
int getopt(int, char * const *, const char *);
#endif
#if (!defined(BSD) || (BSD < 199103))
extern char *optarg;
extern int optind, opterr, optopt;
#endif
/* digital unix needs this but does not give us a way to identify it.
*/
extern int flock(int, int);
/* not all systems who provide flock() provide these definitions.
*/
#ifndef LOCK_SH
# define LOCK_SH 1
#endif
#ifndef LOCK_EX
# define LOCK_EX 2
#endif
#ifndef LOCK_NB
# define LOCK_NB 4
#endif
#ifndef LOCK_UN
# define LOCK_UN 8
#endif
#ifndef WCOREDUMP
# define WCOREDUMP(st) (((st) & 0200) != 0)
#endif
#endif /* CRONIE_EXTERNS_H */

132
src/funcs.h Normal file
View File

@@ -0,0 +1,132 @@
/*
* $Id: funcs.h,v 1.9 2004/01/23 18:56:42 vixie Exp $
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* Notes:
* We should reorg this into sections by module.
*/
#ifndef CRONIE_FUNCS_H
#define CRONIE_FUNCS_H
#include <stdio.h>
#include <sys/types.h>
#ifdef WITH_SELINUX
#include <selinux/selinux.h>
#endif
#include "externs.h"
#include "structs.h"
void set_cron_uid(void),
check_spool_dir(void),
open_logfile(void),
sigpipe_func(void),
job_add(entry *, user *),
do_command(entry *, user *),
link_user(cron_db *, user *),
unlink_user(cron_db *, user *),
free_user(user *),
env_free(char **),
unget_char(int, FILE *),
free_entry(entry *),
acquire_daemonlock(int),
log_it(const char *, PID_T, const char *, const char *, int),
log_close(void),
check_orphans(cron_db *);
#if defined WITH_INOTIFY
void set_cron_watched(int ),
set_cron_unwatched(int ),
check_inotify_database(cron_db *);
#endif
int load_database(cron_db *),
job_runqueue(void),
set_debug_flags(const char *),
get_char(FILE *),
get_string(char *, int, FILE *, const char *),
swap_uids(void),
swap_uids_back(void),
load_env(char *, FILE *),
env_set_from_environ(char ***envpp),
cron_pabort(FILE *),
cron_pclose(FILE *),
glue_strings(char *, size_t, const char *, const char *, char),
strcmp_until(const char *, const char *, char),
skip_comments(FILE *),
allowed(const char * ,const char * ,const char *);
size_t strlens(const char *, ...),
strdtb(char *);
char *env_get(const char *, char **),
*arpadate(time_t *),
*mkprints(unsigned char *, size_t),
*first_word(const char *, const char *),
**env_init(void),
**env_copy(char **),
**env_set(char **, const char *),
**env_update_home(char **, const char *);
user *load_user(int, struct passwd *, const char *, const char *, const char *),
*find_user(cron_db *, const char *, const char *);
entry *load_entry(FILE *, void (*)(), struct passwd *, char **);
FILE *cron_popen(char *, const char *, struct passwd *, char **);
struct passwd *pw_dup(const struct passwd *);
#ifndef HAVE_STRUCT_TM_TM_GMTOFF
long get_gmtoff(time_t *, struct tm *);
#endif
/* Red Hat security stuff (security.c):
*/
void cron_restore_default_security_context( void );
int cron_set_job_security_context( entry *e, user *u, char ***jobenvp );
int cron_open_security_session( struct passwd *pw );
void cron_close_security_session( void );
int cron_change_groups( struct passwd *pw );
int cron_change_user_permanently( struct passwd *pw, char *homedir );
int get_security_context(const char *name,
int crontab_fd,
security_context_t *rcontext,
const char *tabname
);
void free_security_context( security_context_t *scontext );
int crontab_security_access(void);
/* PAM */
#ifdef WITH_PAM
int cron_start_pam(struct passwd *pw);
void cron_close_pam(void);
#endif
#endif /* CRONIE_FUNCS_H */

100
src/globals.h Normal file
View File

@@ -0,0 +1,100 @@
/*
* $Id: globals.h,v 1.10 2004/01/23 19:03:33 vixie Exp $
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Modified 2010/09/12 by Colin Dean, Durham University IT Service,
* to add clustering support.
*/
#ifndef CRONIE_GLOBALS_H
#define CRONIE_GLOBALS_H
#include <time.h>
#include "macros.h"
#ifdef MAIN_PROGRAM
# define XTRN
# define INIT(x) = x
#else
# define XTRN extern
# define INIT(x)
#endif
XTRN const char *copyright[]
#ifdef MAIN_PROGRAM
= {
"@(#) ISC Cron V4.1",
"@(#) Copyright 1988,1989,1990,1993,1994 by Paul Vixie",
"@(#) Copyright 1997,2000 by Internet Software Consortium, Inc.",
"@(#) Copyright 2004 by Internet Systems Consortium, Inc.",
"@(#) All rights reserved",
NULL
}
#endif
;
XTRN const char *MonthNames[]
#ifdef MAIN_PROGRAM
= {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
NULL
}
#endif
;
XTRN const char *DowNames[]
#ifdef MAIN_PROGRAM
= {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
NULL
}
#endif
;
XTRN char *ProgramName;
XTRN int LineNumber;
XTRN int SyslogOutput;
XTRN time_t StartTime;
XTRN int NoFork;
XTRN int PermitAnyCrontab;
XTRN char MailCmd[MAX_COMMAND+1]; /* +1 for terminator */
XTRN char cron_default_mail_charset[MAX_ENVSTR];
XTRN int EnableClustering;
XTRN int ChangePath;
XTRN double RandomScale;
#if DEBUGGING
XTRN int DebugFlags INIT(0);
XTRN const char *DebugFlagNames[]
#ifdef MAIN_PROGRAM
= {
"ext", "sch", "proc", "pars", "load", "misc", "test", "bit",
NULL
}
#endif
;
#else
#define DebugFlags 0
#endif /* DEBUGGING */
#endif /* CRONIE_GLOBALS_H */

107
src/job.c Normal file
View File

@@ -0,0 +1,107 @@
/* Copyright 1988,1990,1993,1994 by Paul Vixie
* All rights reserved
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <stdlib.h>
#include <pwd.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include "funcs.h"
#include "globals.h"
typedef struct _job {
struct _job *next;
entry *e;
user *u;
} job;
static job *jhead = NULL, *jtail = NULL;
void job_add(entry * e, user * u) {
job *j;
struct passwd *newpwd;
struct passwd *temppwd;
const char *uname;
/* if already on queue, keep going */
for (j = jhead; j != NULL; j = j->next)
if (j->e == e && j->u == u)
return;
uname = e->pwd->pw_name;
/* check if user exists in time of job is being run f.e. ldap */
if ((temppwd = getpwnam(uname)) != NULL) {
char **tenvp;
Debug(DSCH | DEXT, ("user [%s:%ld:%ld:...] cmd=\"%s\"\n",
e->pwd->pw_name, (long) temppwd->pw_uid,
(long) temppwd->pw_gid, e->cmd));
if ((newpwd = pw_dup(temppwd)) == NULL) {
log_it(uname, getpid(), "ERROR", "memory allocation failed", errno);
return;
}
free(e->pwd);
e->pwd = newpwd;
if ((tenvp = env_update_home(e->envp, e->pwd->pw_dir)) == NULL) {
log_it(uname, getpid(), "ERROR", "memory allocation failed", errno);
return;
}
e->envp = tenvp;
} else {
log_it(uname, getpid(), "ERROR", "getpwnam() failed - user unknown",errno);
Debug(DSCH | DEXT, ("%s:%d pid=%d time=%lld getpwnam(%s) failed errno=%d error=%s\n",
__FILE__,__LINE__,getpid(),(long long)time(NULL),uname,errno,strerror(errno)));
return;
}
/* build a job queue element */
if ((j = (job *) malloc(sizeof (job))) == NULL)
return;
j->next = NULL;
j->e = e;
j->u = u;
/* add it to the tail */
if (jhead == NULL)
jhead = j;
else
jtail->next = j;
jtail = j;
}
int job_runqueue(void) {
job *j, *jn;
int run = 0;
for (j = jhead; j; j = jn) {
do_command(j->e, j->u);
jn = j->next;
free(j);
run++;
}
jhead = jtail = NULL;
return (run);
}

147
src/macros.h Normal file
View File

@@ -0,0 +1,147 @@
/*
* $Id: macros.h,v 1.9 2004/01/23 18:56:43 vixie Exp $
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef CRONIE_MACROS_H
#define CRONIE_MACROS_H
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
/* these are really immutable, and are
* defined for symbolic convenience only
* TRUE, FALSE, and ERR must be distinct
* ERR must be < OK.
*/
#define TRUE 1
#define FALSE 0
/* system calls return this on success */
#define OK 0
/* or this on error */
#define ERR (-1)
/* turn this on to get '-x' code */
#ifndef DEBUGGING
#define DEBUGGING FALSE
#endif
#define INIT_PID 1 /* parent of orphans */
#define READ_PIPE 0 /* which end of a pipe pair do you read? */
#define WRITE_PIPE 1 /* or write to? */
#define STDIN 0 /* what is stdin's file descriptor? */
#define STDOUT 1 /* stdout's? */
#define STDERR 2 /* stderr's? */
#define ERROR_EXIT 1 /* exit() with this will scare the shell */
#define OK_EXIT 0 /* exit() with this is considered 'normal' */
#define MAX_FNAME PATH_MAX/* max length of internally generated fn */
#define MAX_COMMAND 131072 /* max length of internally generated cmd (max sh cmd line length) */
#define MAX_ENVSTR 131072 /* max length of envvar=value\0 strings */
#define MAX_TEMPSTR 131072 /* obvious */
#define MAX_UNAME 256 /* max length of username */
#define ROOT_UID 0 /* don't change this, it really must be root */
#define ROOT_USER "root" /* ditto */
#define MAX_USER_ENVS 1000 /* maximum environment variables in user's crontab */
#define MAX_USER_ENTRIES 10000 /* maximum crontab entries in user's crontab */
#define MAX_GARBAGE 32768 /* max num of chars of comments and whitespaces between entries */
#define MAX_CLOSE_FD 10000 /* max fd num to close when spawning a child process */
/* NOTE: these correspond to DebugFlagNames,
* defined below.
*/
#define DEXT 0x0001 /* extend flag for other debug masks */
#define DSCH 0x0002 /* scheduling debug mask */
#define DPROC 0x0004 /* process control debug mask */
#define DPARS 0x0008 /* parsing debug mask */
#define DLOAD 0x0010 /* database loading debug mask */
#define DMISC 0x0020 /* misc debug mask */
#define DTEST 0x0040 /* test mode: don't execute any commands */
#define PPC_NULL ((const char **)NULL)
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif
#define Skip_Blanks(c, f) \
while (c == '\t' || c == ' ') \
c = get_char(f);
#define Skip_Nonblanks(c, f) \
while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \
c = get_char(f);
#if DEBUGGING
# define Debug(mask, message) \
if ((DebugFlags & (mask)) != 0) \
printf message
#else /* !DEBUGGING */
# define Debug(mask, message) \
()
#endif /* DEBUGGING */
#define MkUpper(ch) (islower(ch) ? toupper(ch) : ch)
#define Set_LineNum(ln) {Debug(DPARS|DEXT,("linenum=%d\n",ln)); \
LineNumber = ln; \
}
#ifdef HAVE_STRUCT_TM_TM_GMTOFF
#define get_gmtoff(c, t) ((t)->tm_gmtoff)
#endif
#define SECONDS_PER_MINUTE 60
#define SECONDS_PER_HOUR 3600
#define FIRST_MINUTE 0
#define LAST_MINUTE 59
#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1)
#define FIRST_HOUR 0
#define LAST_HOUR 23
#define HOUR_COUNT (LAST_HOUR - FIRST_HOUR + 1)
#define FIRST_DOM 1
#define LAST_DOM 31
#define DOM_COUNT (LAST_DOM - FIRST_DOM + 1)
#define FIRST_MONTH 1
#define LAST_MONTH 12
#define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1)
/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */
#define FIRST_DOW 0
#define LAST_DOW 7
#define DOW_COUNT (LAST_DOW - FIRST_DOW + 1)
#define TMAX(a,b) ((a)>(b)?(a):(b))
#define TMIN(a,b) ((a)<(b)?(a):(b))
/*
* Because crontab/at files may be owned by their respective users we
* take extreme care in opening them. If the OS lacks the O_NOFOLLOW
* we will just have to live without it. In order for this to be an
* issue an attacker would have to subvert group CRON_GROUP.
*/
#include <fcntl.h>
#ifndef O_NOFOLLOW
#define O_NOFOLLOW 0
#endif
#endif /* CRONIE_MACROS_H */

722
src/misc.c Normal file
View File

@@ -0,0 +1,722 @@
/* Copyright 1988,1990,1993,1994 by Paul Vixie
* All rights reserved
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* vix 26jan87 [RCS has the rest of the log]
* vix 30dec86 [written]
*/
#include "config.h"
#include "globals.h"
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#if defined(SYSLOG)
# include <syslog.h>
#endif
#ifdef WITH_AUDIT
# include <libaudit.h>
#endif
#ifdef HAVE_FCNTL_H /* fcntl(2) */
# include <fcntl.h>
#endif
#ifdef HAVE_UNISTD_H /* lockf(3) */
# include <unistd.h>
#endif
#ifdef HAVE_FLOCK /* flock(2) */
# include <sys/file.h>
#endif
#include "funcs.h"
#include "macros.h"
#include "pathnames.h"
#if defined(SYSLOG) && defined(LOG_FILE)
# undef LOG_FILE
#endif
#if defined(LOG_DAEMON) && !defined(LOG_CRON)
# define LOG_CRON LOG_DAEMON
#endif
#ifndef FACILITY
# define FACILITY LOG_CRON
#endif
static int LogFD = ERR;
#if defined(SYSLOG)
static int syslog_open = FALSE;
#endif
#if defined(HAVE_FLOCK)
# define trylock_file(fd) flock((fd), LOCK_EX|LOCK_NB)
#elif defined(HAVE_FCNTL) && defined(F_SETLK)
static int trylock_file(int fd) {
struct flock fl;
memset(&fl, '\0', sizeof (fl));
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
return fcntl(fd, F_SETLK, &fl);
}
#elif defined(HAVE_LOCKF)
# define trylock_file(fd) lockf((fd), F_TLOCK, 0)
#endif
/*
* glue_strings is the overflow-safe equivalent of
* sprintf(buffer, "%s%c%s", a, separator, b);
*
* returns 1 on success, 0 on failure. 'buffer' MUST NOT be used if
* glue_strings fails.
*/
int
glue_strings(char *buffer, size_t buffer_size, const char *a, const char *b,
char separator) {
char *buf;
char *buf_end;
if (buffer_size <= 0)
return (0);
buf_end = buffer + buffer_size;
buf = buffer;
for ( /* nothing */ ; buf < buf_end && *a != '\0'; buf++, a++)
*buf = *a;
if (buf == buf_end)
return (0);
if (separator != '/' || buf == buffer || buf[-1] != '/')
*buf++ = separator;
if (buf == buf_end)
return (0);
for ( /* nothing */ ; buf < buf_end && *b != '\0'; buf++, b++)
*buf = *b;
if (buf == buf_end)
return (0);
*buf = '\0';
return (1);
}
int strcmp_until(const char *left, const char *right, char until) {
while (*left && *left != until && *left == *right) {
left++;
right++;
}
if ((*left == '\0' || *left == until) && (*right == '\0' ||
*right == until)) {
return (0);
}
return (*left - *right);
}
/* strdtb(s) - delete trailing blanks in string 's' and return new length
*/
size_t strdtb(char *s) {
char *x = s;
/* scan forward to the null
*/
while (*x)
x++;
/* scan backward to either the first character before the string,
* or the last non-blank in the string, whichever comes first.
*/
do {
x--;
} while (x >= s && isspace((unsigned char) *x));
/* one character beyond where we stopped above is where the null
* goes.
*/
*++x = '\0';
/* the difference between the position of the null character and
* the position of the first character of the string is the length.
*/
return ((size_t)(x - s));
}
int set_debug_flags(const char *flags) {
/* debug flags are of the form flag[,flag ...]
*
* if an error occurs, print a message to stdout and return FALSE.
* otherwise return TRUE after setting ERROR_FLAGS.
*/
#if !DEBUGGING
printf("this program was compiled without debugging enabled\n");
return (FALSE);
#else /* DEBUGGING */
const char *pc = flags;
DebugFlags = 0;
while (*pc) {
const char **test;
int mask;
/* try to find debug flag name in our list.
*/
for (test = DebugFlagNames, mask = 1;
*test != NULL && strcmp_until(*test, pc, ','); test++, mask <<= 1) ;
if (!*test) {
fprintf(stderr, "unrecognized debug flag <%s> <%s>\n", flags, pc);
return (FALSE);
}
DebugFlags |= mask;
/* skip to the next flag
*/
while (*pc && *pc != ',')
pc++;
if (*pc == ',')
pc++;
}
if (DebugFlags) {
int flag;
fprintf(stderr, "debug flags enabled:");
for (flag = 0; DebugFlagNames[flag]; flag++)
if (DebugFlags & (1 << flag))
fprintf(stderr, " %s", DebugFlagNames[flag]);
fprintf(stderr, "\n");
}
return (TRUE);
#endif /* DEBUGGING */
}
void set_cron_uid(void) {
#if defined(BSD) || defined(POSIX)
if (seteuid(ROOT_UID) < OK) {
perror("seteuid");
exit(ERROR_EXIT);
}
#else
if (setuid(ROOT_UID) < OK) {
perror("setuid");
exit(ERROR_EXIT);
}
#endif
}
void check_spool_dir(void) {
struct stat sb;
#ifdef CRON_GROUP
struct group *grp = NULL;
grp = getgrnam(CRON_GROUP);
#endif
/* check SPOOL_DIR existence
*/
if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) {
perror(SPOOL_DIR);
if (OK == mkdir(SPOOL_DIR, 0700)) {
fprintf(stderr, "%s: created\n", SPOOL_DIR);
if (stat(SPOOL_DIR, &sb) < OK) {
perror("stat retry");
exit(ERROR_EXIT);
}
}
else {
fprintf(stderr, "%s: ", SPOOL_DIR);
perror("mkdir");
exit(ERROR_EXIT);
}
}
if (!S_ISDIR(sb.st_mode)) {
fprintf(stderr, "'%s' is not a directory, bailing out.\n", SPOOL_DIR);
exit(ERROR_EXIT);
}
#ifdef CRON_GROUP
if (grp != NULL) {
if (sb.st_gid != grp->gr_gid)
if (chown(SPOOL_DIR, -1, grp->gr_gid) == -1) {
fprintf(stderr, "chown %s failed: %s\n", SPOOL_DIR,
strerror(errno));
exit(ERROR_EXIT);
}
if (sb.st_mode != 01730)
if (chmod(SPOOL_DIR, 01730) == -1) {
fprintf(stderr, "chmod 01730 %s failed: %s\n", SPOOL_DIR,
strerror(errno));
exit(ERROR_EXIT);
}
}
#endif
}
/* acquire_daemonlock() - write our PID into /etc/cron.pid, unless
* another daemon is already running, which we detect here.
*
* note: main() calls us twice; once before forking, once after.
* we maintain static storage of the file pointer so that we
* can rewrite our PID into _PATH_CRON_PID after the fork.
*/
void acquire_daemonlock(int closeflag) {
static int fd = -1;
char buf[3 * MAX_FNAME];
const char *pidfile;
char *ep;
long otherpid = -1;
ssize_t num, len;
pid_t pid = getpid();
if (closeflag) {
/* close stashed fd for child so we don't leak it. */
if (fd != -1) {
close(fd);
fd = -1;
}
/* and restore default sig handlers so we don't remove pid file if killed */
signal(SIGINT,SIG_DFL);
signal(SIGTERM,SIG_DFL);
return;
}
if (fd == -1) {
pidfile = _PATH_CRON_PID;
/* Initial mode is 0600 to prevent flock() race/DoS. */
if ((fd = open(pidfile, O_RDWR | O_CREAT, 0600)) == -1) {
int save_errno = errno;
sprintf(buf, "can't open or create %s", pidfile);
fprintf(stderr, "%s: %s: %s\n", ProgramName, buf,
strerror(save_errno));
log_it("CRON", pid, "DEATH", buf, save_errno);
exit(ERROR_EXIT);
}
if (trylock_file(fd) < OK) {
int save_errno = errno;
memset(buf, 0, sizeof (buf));
if ((num = read(fd, buf, sizeof (buf) - 1)) > 0 &&
(otherpid = strtol(buf, &ep, 10)) > 0 &&
ep != buf && *ep == '\n' && otherpid != LONG_MAX) {
snprintf(buf, sizeof (buf),
"can't lock %s, otherpid may be %ld", pidfile, otherpid);
}
else {
snprintf(buf, sizeof (buf),
"can't lock %s, otherpid unknown", pidfile);
}
fprintf(stderr, "%s: %s: %s\n", ProgramName, buf,
strerror(save_errno));
log_it("CRON", pid, "DEATH", buf, save_errno);
exit(ERROR_EXIT);
}
(void) fchmod(fd, 0644);
(void) fcntl(fd, F_SETFD, 1);
}
#if !defined(HAVE_FLOCK)
else {
/* Racy but better than nothing, just hope the parent exits */
sleep(0);
trylock_file(fd);
}
#endif
sprintf(buf, "%ld\n", (long) pid);
(void) lseek(fd, (off_t) 0, SEEK_SET);
len = (ssize_t)strlen(buf);
if ((num = write(fd, buf, (size_t)len)) != len)
log_it("CRON", pid, "ERROR", "write() failed", errno);
else {
if (ftruncate(fd, num) == -1)
log_it("CRON", pid, "ERROR", "ftruncate() failed", errno);
}
/* abandon fd even though the file is open. we need to keep
* it open and locked, but we don't need the handles elsewhere.
*/
}
/* get_char(file) : like getc() but increment LineNumber on newlines
*/
int get_char(FILE * file) {
int ch;
ch = getc(file);
if (ch == '\n')
Set_LineNum(LineNumber + 1)
return (ch);
}
/* unget_char(ch, file) : like ungetc but do LineNumber processing
*/
void unget_char(int ch, FILE * file) {
ungetc(ch, file);
if (ch == '\n')
Set_LineNum(LineNumber - 1)
}
/* get_string(str, max, file, termstr) : like fgets() but
* (1) has terminator string which should include \n
* (2) will always leave room for the null
* (3) uses get_char() so LineNumber will be accurate
* (4) returns EOF or terminating character, whichever
*/
int get_string(char *string, int size, FILE * file, const char *terms) {
int ch;
while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) {
if (size > 1) {
*string++ = (char) ch;
size--;
}
}
if (size > 0)
*string = '\0';
return (ch);
}
/* skip_comments(file) : read past comment (if any)
*/
int skip_comments(FILE * file) {
int ch;
int n = 0;
while (EOF != (ch = get_char(file))) {
/* ch is now the first character of a line.
*/
if (++n > MAX_GARBAGE)
return FALSE;
while (ch == ' ' || ch == '\t') {
ch = get_char(file);
if (++n > MAX_GARBAGE)
return FALSE;
}
if (ch == EOF)
break;
/* ch is now the first non-blank character of a line.
*/
if (ch != '\n' && ch != '#')
break;
/* ch must be a newline or comment as first non-blank
* character on a line.
*/
while (ch != '\n' && ch != EOF) {
ch = get_char(file);
if (++n > MAX_GARBAGE)
return FALSE;
}
/* ch is now the newline of a line which we're going to
* ignore.
*/
}
if (ch != EOF)
unget_char(ch, file);
return TRUE;
}
void log_it(const char *username, PID_T xpid, const char *event,
const char *detail, int err) {
#if defined(LOG_FILE) || DEBUGGING
PID_T pid = xpid;
#endif
#if defined(LOG_FILE)
char *msg;
TIME_T now = time((TIME_T) 0);
struct tm *t = localtime(&now);
int msg_size;
#endif
#if defined(LOG_FILE)
/* we assume that MAX_TEMPSTR will hold the date, time, &punctuation.
*/
msg = malloc(msg_size = (strlen(username)
+ strlen(event)
+ strlen(detail)
+ MAX_TEMPSTR)
);
if (msg == NULL) { /* damn, out of mem and we did not test that before... */
fprintf(stderr, "%s: Run OUT OF MEMORY while %s\n",
ProgramName, __FUNCTION__);
return;
}
if (LogFD < OK) {
LogFD = open(LOG_FILE, O_WRONLY | O_APPEND | O_CREAT, 0600);
if (LogFD < OK) {
fprintf(stderr, "%s: can't open log file\n", ProgramName);
perror(LOG_FILE);
}
else {
(void) fcntl(LogFD, F_SETFD, 1);
}
}
/* we have to snprintf() it because fprintf() doesn't always write
* everything out in one chunk and this has to be atomically appended
* to the log file.
*/
snprintf(msg, msg_size,
"%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)%s%s\n", username,
t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, pid,
event, detail, err != 0 ? ": " : "", err != 0 ? strerror(err) : "");
/* we have to run strlen() because sprintf() returns (char*) on old BSD
*/
if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) {
if (LogFD >= OK)
perror(LOG_FILE);
fprintf(stderr, "%s: can't write to log file\n", ProgramName);
write(STDERR, msg, strlen(msg));
}
free(msg);
#endif /*LOG_FILE */
#if defined(SYSLOG)
if (!syslog_open) {
# ifdef LOG_DAEMON
openlog(ProgramName, LOG_PID, FACILITY);
# else
openlog(ProgramName, LOG_PID);
# endif
syslog_open = TRUE; /* assume openlog success */
}
syslog(err != 0 ? LOG_ERR : LOG_INFO,
"(%s) %s (%s)%s%s", username, event, detail,
err != 0 ? ": " : "", err != 0 ? strerror(err) : "");
#endif /*SYSLOG*/
#if DEBUGGING
if (DebugFlags) {
fprintf(stderr, "log_it: (%s %ld) %s (%s)%s%s\n",
username, (long) pid, event, detail,
err != 0 ? ": " : "", err != 0 ? strerror(err) : "");
}
#endif
}
void log_close(void) {
if (LogFD != ERR) {
close(LogFD);
LogFD = ERR;
}
#if defined(SYSLOG)
closelog();
syslog_open = FALSE;
#endif /*SYSLOG*/
}
/* char *first_word(const char *s, const char *t)
* return pointer to first word
* parameters:
* s - string we want the first word of
* t - terminators, implicitly including \0
* warnings:
* (1) this routine is fairly slow
* (2) it returns a pointer to static storage
*/
char *first_word(const char *s, const char *t) {
static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */
static int retsel = 0;
char *rb, *rp;
/* select a return buffer */
retsel = 1 - retsel;
rb = &retbuf[retsel][0];
rp = rb;
/* skip any leading terminators */
while (*s && (NULL != strchr(t, *s))) {
s++;
}
/* copy until next terminator or full buffer */
while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) {
*rp++ = *s++;
}
/* finish the return-string and return it */
*rp = '\0';
return (rb);
}
/* warning:
* heavily ascii-dependent.
*/
static void mkprint(char *dst, unsigned char *src, size_t len) {
/*
* XXX
* We know this routine can't overflow the dst buffer because mkprints()
* allocated enough space for the worst case.
*/
while (len-- > 0) {
unsigned char ch = *src++;
if (ch < ' ') { /* control character */
*dst++ = '^';
*dst++ = (char)(ch + '@');
}
else if (ch < 0177) { /* printable */
*dst++ = (char)ch;
}
else if (ch == 0177) { /* delete/rubout */
*dst++ = '^';
*dst++ = '?';
}
else { /* parity character */
sprintf(dst, "\\%03o", ch);
dst += 4;
}
}
*dst = '\0';
}
/* warning:
* returns a pointer to malloc'd storage, you must call free yourself.
*/
char *mkprints(unsigned char *src, size_t len) {
char *dst = malloc(len * 4 + 1);
if (dst)
mkprint(dst, src, len);
return (dst);
}
#ifdef MAIL_DATE
/* Sat, 27 Feb 1993 11:44:51 -0800 (CST)
* 1234567890123456789012345678901234567
*/
char *arpadate(time_t *clock) {
time_t t = clock ? *clock : time((TIME_T) 0);
struct tm tm = *localtime(&t);
long gmtoff = get_gmtoff(&t, &tm);
int hours = gmtoff / SECONDS_PER_HOUR;
int minutes =
(gmtoff - (hours * SECONDS_PER_HOUR)) / SECONDS_PER_MINUTE;
static char ret[64]; /* zone name might be >3 chars */
(void) sprintf(ret, "%s, %2d %s %2d %02d:%02d:%02d %.2d%.2d (%s)",
DowNames[tm.tm_wday],
tm.tm_mday,
MonthNames[tm.tm_mon],
tm.tm_year + 1900,
tm.tm_hour, tm.tm_min, tm.tm_sec, hours, minutes, TZONE(tm));
return (ret);
}
#endif /*MAIL_DATE */
#ifdef HAVE_SAVED_UIDS
static uid_t save_euid;
static gid_t save_egid;
int swap_uids(void) {
save_egid = getegid();
save_euid = geteuid();
return ((setegid(getgid()) || seteuid(getuid()))? -1 : 0);
}
int swap_uids_back(void) {
return ((setegid(save_egid) || seteuid(save_euid)) ? -1 : 0);
}
#else /*HAVE_SAVED_UIDS */
int swap_uids(void) {
return ((setregid(getegid(), getgid())
|| setreuid(geteuid(), getuid())) ? -1 : 0);
}
int swap_uids_back(void) {
return (swap_uids());
}
#endif /*HAVE_SAVED_UIDS */
size_t strlens(const char *last, ...) {
va_list ap;
size_t ret = 0;
const char *str;
va_start(ap, last);
for (str = last; str != NULL; str = va_arg(ap, const char *))
ret += strlen(str);
va_end(ap);
return (ret);
}
/* Return the offset from GMT in seconds (algorithm taken from sendmail).
*
* warning:
* clobbers the static storage space used by localtime() and gmtime().
* If the local pointer is non-NULL it *must* point to a local copy.
*/
#ifndef HAVE_STRUCT_TM_TM_GMTOFF
long get_gmtoff(time_t * clock, struct tm *local) {
struct tm gmt;
long offset;
gmt = *gmtime(clock);
if (local == NULL)
local = localtime(clock);
offset = (local->tm_sec - gmt.tm_sec) +
((local->tm_min - gmt.tm_min) * 60) +
((local->tm_hour - gmt.tm_hour) * 3600);
/* Timezone may cause year rollover to happen on a different day. */
if (local->tm_year < gmt.tm_year)
offset -= 24 * 3600;
else if (local->tm_year > gmt.tm_year)
offset += 24 * 3600;
else if (local->tm_yday < gmt.tm_yday)
offset -= 24 * 3600;
else if (local->tm_yday > gmt.tm_yday)
offset += 24 * 3600;
return (offset);
}
#endif /* HAVE_STRUCT_TM_TM_GMTOFF */

68
src/pathnames.h Normal file
View File

@@ -0,0 +1,68 @@
/* Copyright 1993,1994 by Paul Vixie
* All rights reserved
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* $Id: pathnames.h,v 1.9 2004/01/23 18:56:43 vixie Exp $
*/
#ifndef _PATHNAMES_H_
#define _PATHNAMES_H_
#if (defined(BSD)) && (BSD >= 199103) || defined(__linux) || defined(AIX)
# include <paths.h>
#endif /*BSD*/
#include "cron-paths.h"
/* where should the daemon stick its PID?
* PIDDIR must end in '/'.
* (Don't ask why the default is "/etc/".)
*/
#ifdef CRON_PID_DIR
# define PIDDIR CRON_PID_DIR "/"
#else
# ifdef _PATH_VARRUN
# define PIDDIR _PATH_VARRUN
# else
# define PIDDIR SYSCONFDIR "/"
# endif
#endif
#define PIDFILE "crond.pid"
#define _PATH_CRON_PID PIDDIR PIDFILE
#define REBOOT_LOCK PIDDIR "cron.reboot"
#ifndef _PATH_BSHELL
# define _PATH_BSHELL "/bin/sh"
#endif
#ifndef _PATH_STDPATH
# define _PATH_STDPATH "/usr/bin:/bin:/usr/sbin:/sbin"
#endif
#ifndef _PATH_TMP
# define _PATH_TMP "/tmp"
#endif
#ifndef _PATH_DEVNULL
# define _PATH_DEVNULL "/dev/null"
#endif
#endif /* _PATHNAMES_H_ */

221
src/popen.c Normal file
View File

@@ -0,0 +1,221 @@
/* $NetBSD: popen.c,v 1.9 2005/03/16 02:53:55 xtraeme Exp $ */
/*
* Copyright (c) 1988, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software written by Ken Arnold and
* published in UNIX Review, Vol. 6, No. 8.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
#include "config.h"
#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include "funcs.h"
#include "globals.h"
#include "macros.h"
#ifdef HAVE_SYS_CDEFS_H
# include <sys/cdefs.h>
#endif
#include <signal.h>
/*
* Special version of popen which avoids call to shell. This insures no one
* may create a pipe to a hidden program as a side effect of a list or dir
* command.
*/
static PID_T *pids;
static int fds;
#define MAX_ARGS 1024
FILE *cron_popen(char *program, const char *type, struct passwd *pw, char **jobenv) {
char *cp;
FILE *iop;
int argc, pdes[2];
PID_T pid;
char *argv[MAX_ARGS];
ssize_t out;
char buf[PIPE_BUF];
struct sigaction sa;
int fd;
#ifdef __GNUC__
(void) &iop; /* Avoid fork clobbering */
#endif
if ((*type != 'r' && *type != 'w') || type[1])
return (NULL);
if (!pids) {
if ((fds = getdtablesize()) <= 0)
return (NULL);
if (fds > MAX_CLOSE_FD)
fds = MAX_CLOSE_FD; /* avoid allocating too much memory */
if (!(pids = (PID_T *) malloc((u_int) ((size_t)fds * sizeof (PID_T)))))
return (NULL);
memset((char *) pids, 0, (size_t)fds * sizeof (PID_T));
}
if (pipe(pdes) < 0)
return (NULL);
if (pdes[0] >= fds || pdes[1] >= fds) {
(void) close(pdes[0]);
(void) close(pdes[1]);
return NULL;
}
/* break up string into pieces */
for (argc = 0, cp = program; argc < MAX_ARGS; cp = NULL)
if (!(argv[argc++] = strtok(cp, " \t\n")))
break;
iop = NULL;
switch (pid = fork()) {
case -1: /* error */
(void) close(pdes[0]);
(void) close(pdes[1]);
goto pfree;
/* NOTREACHED */
case 0: /* child */
if (*type == 'r') {
if (pdes[1] != STDOUT) {
dup2(pdes[1], STDOUT);
dup2(pdes[1], STDERR); /* stderr, too! */
(void) close(pdes[1]);
}
(void) close(pdes[0]);
}
else {
if (pdes[0] != STDIN) {
dup2(pdes[0], STDIN);
(void) close(pdes[0]);
}
(void) close(pdes[1]);
}
/* reset SIGPIPE to default for the child */
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_DFL;
sigaction(SIGPIPE, &sa, NULL);
/* close all unwanted open file descriptors */
for (fd = STDERR + 1; fd < fds; fd++) {
close(fd);
}
if (cron_change_user_permanently(pw, env_get("HOME", jobenv)) != 0)
_exit(2);
if (execvpe(argv[0], argv, jobenv) < 0) {
int save_errno = errno;
log_it("CRON", getpid(), "EXEC FAILED", program, save_errno);
if (*type != 'r') {
while (0 != (out = read(STDIN, buf, PIPE_BUF))) {
if ((out == -1) && (errno != EINTR))
break;
}
}
}
_exit(1);
}
/* parent; assume fdopen can't fail... */
if (*type == 'r') {
fd = pdes[0];
iop = fdopen(pdes[0], type);
(void) close(pdes[1]);
}
else {
fd = pdes[1];
iop = fdopen(pdes[1], type);
(void) close(pdes[0]);
}
pids[fd] = pid;
pfree:
return (iop);
}
static int cron_finalize(FILE * iop, int sig) {
int fdes;
sigset_t oset, nset;
WAIT_T stat_loc;
PID_T pid;
/*
* pclose returns -1 if stream is not associated with a
* `popened' command, or, if already `pclosed'.
*/
fdes = fileno(iop);
if (pids == NULL || fdes >= fds || pids[fdes] == 0L)
return (-1);
if (!sig) {
(void) fclose(iop);
} else if (kill(pids[fdes], sig) == -1) {
return -1;
}
sigemptyset(&nset);
sigaddset(&nset, SIGINT);
sigaddset(&nset, SIGQUIT);
sigaddset(&nset, SIGHUP);
(void) sigprocmask(SIG_BLOCK, &nset, &oset);
while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1) ;
(void) sigprocmask(SIG_SETMASK, &oset, NULL);
if (sig) {
(void) fclose(iop);
}
pids[fdes] = 0;
if (pid < 0) {
return pid;
}
if (WIFEXITED(stat_loc)) {
return WEXITSTATUS(stat_loc);
} else {
return WTERMSIG(stat_loc);
}
}
int cron_pclose(FILE * iop) {
return cron_finalize(iop, 0);
}
int cron_pabort(FILE * iop) {
int esig = cron_finalize(iop, SIGKILL);
return esig == SIGKILL ? 0 : esig;
}

128
src/pw_dup.c Normal file
View File

@@ -0,0 +1,128 @@
/*
* Copyright (c) 2000,2002 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE
* FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <sys/param.h>
#if !defined(OpenBSD) || OpenBSD < 200105
#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "funcs.h"
#include "globals.h"
struct passwd *
pw_dup(const struct passwd *pw) {
char *cp;
size_t nsize=0, psize=0, gsize=0, dsize=0, ssize=0, total;
struct passwd *newpw;
/* Allocate in one big chunk for easy freeing */
total = sizeof(struct passwd);
if (pw->pw_name) {
nsize = strlen(pw->pw_name) + 1;
total += nsize;
}
if (pw->pw_passwd) {
psize = strlen(pw->pw_passwd) + 1;
total += psize;
}
#ifdef LOGIN_CAP
if (pw->pw_class) {
csize = strlen(pw->pw_class) + 1;
total += csize;
}
#endif /* LOGIN_CAP */
if (pw->pw_gecos) {
gsize = strlen(pw->pw_gecos) + 1;
total += gsize;
}
if (pw->pw_dir) {
dsize = strlen(pw->pw_dir) + 1;
total += dsize;
}
if (pw->pw_shell) {
ssize = strlen(pw->pw_shell) + 1;
total += ssize;
}
if ((cp = malloc(total)) == NULL)
return (NULL);
newpw = (struct passwd *)cp;
/*
* Copy in passwd contents and make strings relative to space
* at the end of the buffer.
*/
(void)memcpy(newpw, pw, sizeof(struct passwd));
cp += sizeof(struct passwd);
if (pw->pw_name) {
(void)memcpy(cp, pw->pw_name, nsize);
newpw->pw_name = cp;
cp += nsize;
}
if (pw->pw_passwd) {
(void)memcpy(cp, pw->pw_passwd, psize);
newpw->pw_passwd = cp;
cp += psize;
}
#ifdef LOGIN_CAP
if (pw->pw_class) {
(void)memcpy(cp, pw->pw_class, csize);
newpw->pw_class = cp;
cp += csize;
}
#endif /* LOGIN_CAP */
if (pw->pw_gecos) {
(void)memcpy(cp, pw->pw_gecos, gsize);
newpw->pw_gecos = cp;
cp += gsize;
}
if (pw->pw_dir) {
(void)memcpy(cp, pw->pw_dir, dsize);
newpw->pw_dir = cp;
cp += dsize;
}
if (pw->pw_shell) {
(void)memcpy(cp, pw->pw_shell, ssize);
newpw->pw_shell = cp;
cp += ssize;
}
/* cppcheck-suppress[memleak symbolName=cp] memory originally pointed to by cp returned via newpw */
return (newpw);
}
#endif /* !OpenBSD || OpenBSD < 200105 */

748
src/security.c Normal file
View File

@@ -0,0 +1,748 @@
/* security.c
*
* Implement Red Hat crond security context transitions
*
* Jason Vas Dias <jvdias@redhat.com> January 2006
*
* Copyright(C) Red Hat Inc., 2006
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "cronie_common.h"
#include "funcs.h"
#include "globals.h"
#include "macros.h"
#ifdef WITH_PAM
# include <security/pam_appl.h>
#endif
#ifdef WITH_SELINUX
# include <selinux/selinux.h>
# include <selinux/context.h>
# include <selinux/get_context_list.h>
#endif
#ifdef WITH_AUDIT
# include <libaudit.h>
#endif
#ifdef WITH_PAM
static pam_handle_t *pamh = NULL;
static int pam_session_opened = 0; //global for open session
static int
cron_conv(int num_msg, const struct pam_message **msgm,
struct pam_response **response ATTRIBUTE_UNUSED,
void *appdata_ptr ATTRIBUTE_UNUSED)
{
int i;
for (i = 0; i < num_msg; i++) {
switch (msgm[i]->msg_style) {
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
if (msgm[i]->msg != NULL) {
log_it("CRON", getpid(), "pam_message", msgm[i]->msg, 0);
}
break;
default:
break;
}
}
return (0);
}
static const struct pam_conv conv = {
cron_conv, NULL
};
static int cron_open_pam_session(struct passwd *pw);
# define PAM_FAIL_CHECK if (retcode != PAM_SUCCESS) { \
log_it(pw->pw_name, getpid(), "PAM ERROR", pam_strerror(pamh, retcode), 0); \
if (pamh != NULL) { \
if (pam_session_opened != 0) \
pam_close_session(pamh, PAM_SILENT); \
pam_end(pamh, retcode); \
pamh = NULL; \
} \
return(retcode); }
#endif
static char **build_env(char **cronenv);
#ifdef WITH_SELINUX
static int cron_change_selinux_range(user * u, security_context_t ucontext);
static int cron_get_job_range(user * u, security_context_t * ucontextp,
char **jobenv);
#endif
void cron_restore_default_security_context(void) {
#ifdef WITH_SELINUX
if (is_selinux_enabled() <= 0)
return;
if (setexeccon(NULL) < 0)
log_it("CRON", getpid(), "ERROR",
"failed to restore SELinux context", 0);
#endif
}
int cron_set_job_security_context(entry *e, user *u ATTRIBUTE_UNUSED,
char ***jobenv) {
time_t minutely_time = 0;
#ifdef WITH_PAM
int ret;
#endif
if ((e->flags & MIN_STAR) == MIN_STAR) {
/* "minute-ly" job: Every minute for given hour/dow/month/dom.
* Ensure that these jobs never run in the same minute:
*/
minutely_time = time(NULL);
Debug(DSCH, ("Minute-ly job. Recording time %lld\n", (long long)minutely_time));
}
#ifdef WITH_PAM
/* PAM is called only for non-root users or non-system crontab */
if ((!u->system || e->pwd->pw_uid != 0) && (ret = cron_start_pam(e->pwd)) != 0) {
log_it(e->pwd->pw_name, getpid(), "FAILED to authorize user with PAM",
pam_strerror(pamh, ret), 0);
return -1;
}
#endif
#ifdef WITH_SELINUX
/* we must get the crontab context BEFORE changing user, else
* we'll not be permitted to read the cron spool directory :-)
*/
security_context_t ucontext = 0;
if (cron_get_job_range(u, &ucontext, e->envp) < OK) {
log_it(e->pwd->pw_name, getpid(), "ERROR",
"failed to get SELinux context", 0);
return -1;
}
if (cron_change_selinux_range(u, ucontext) != 0) {
log_it(e->pwd->pw_name, getpid(), "ERROR",
"failed to change SELinux context", 0);
if (ucontext)
freecon(ucontext);
return -1;
}
if (ucontext)
freecon(ucontext);
#endif
#ifdef WITH_PAM
if (pamh != NULL && (ret = cron_open_pam_session(e->pwd)) != 0) {
log_it(e->pwd->pw_name, getpid(),
"FAILED to open PAM security session", pam_strerror(pamh, ret), 0);
return -1;
}
#endif
if (cron_change_groups(e->pwd) != 0) {
return -1;
}
*jobenv = build_env(e->envp);
time_t job_run_time = time(NULL);
if ((minutely_time > 0) && ((job_run_time / 60) != (minutely_time / 60))) {
/* if a per-minute job is delayed into the next minute
* (eg. by network authentication method timeouts), skip it.
*/
struct tm tmS, tmN;
char buf[256];
localtime_r(&job_run_time, &tmN);
localtime_r(&minutely_time, &tmS);
snprintf(buf, sizeof (buf),
"Job execution of per-minute job scheduled for "
"%.2u:%.2u delayed into subsequent minute %.2u:%.2u. Skipping job run.",
tmS.tm_hour, tmS.tm_min, tmN.tm_hour, tmN.tm_min);
log_it(e->pwd->pw_name, getpid(), "INFO", buf, 0);
return -1;
}
return 0;
}
#if defined(WITH_PAM)
int cron_start_pam(struct passwd *pw) {
int retcode = 0;
retcode = pam_start("crond", pw->pw_name, &conv, &pamh);
PAM_FAIL_CHECK;
retcode = pam_set_item(pamh, PAM_TTY, "cron");
PAM_FAIL_CHECK;
retcode = pam_acct_mgmt(pamh, PAM_SILENT);
PAM_FAIL_CHECK;
retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED | PAM_SILENT);
PAM_FAIL_CHECK;
return retcode;
}
#endif
#if defined(WITH_PAM)
static int cron_open_pam_session(struct passwd *pw) {
int retcode;
retcode = pam_open_session(pamh, PAM_SILENT);
PAM_FAIL_CHECK;
if (retcode == PAM_SUCCESS)
pam_session_opened = 1;
return retcode;
}
#endif
void cron_close_pam(void) {
#if defined(WITH_PAM)
if (pam_session_opened != 0) {
pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT);
pam_close_session(pamh, PAM_SILENT);
}
if (pamh != NULL) {
pam_end(pamh, PAM_SUCCESS);
pamh = NULL;
}
#endif
}
int cron_change_groups(struct passwd *pw) {
pid_t pid = getpid();
if (setgid(pw->pw_gid) != 0) {
log_it("CRON", pid, "ERROR", "setgid failed", errno);
return -1;
}
if (initgroups(pw->pw_name, pw->pw_gid) != 0) {
log_it("CRON", pid, "ERROR", "initgroups failed", errno);
return -1;
}
#if defined(WITH_PAM)
/* credentials may take form of supplementary groups so reinitialize
* them here */
if (pamh != NULL) {
pam_setcred(pamh, PAM_REINITIALIZE_CRED | PAM_SILENT);
}
#endif
return 0;
}
int cron_change_user_permanently(struct passwd *pw, char *homedir) {
if (setreuid(pw->pw_uid, pw->pw_uid) != 0) {
log_it("CRON", getpid(), "ERROR", "setreuid failed", errno);
return -1;
}
if (chdir(homedir) == -1) {
log_it("CRON", getpid(), "ERROR chdir failed", homedir, errno);
return -1;
}
log_close();
return 0;
}
#ifdef WITH_SELINUX
static int cron_authorize_context(security_context_t scontext,
security_context_t file_context) {
struct av_decision avd;
int retval;
security_class_t tclass;
access_vector_t bit;
tclass = string_to_security_class("file");
if (!tclass) {
log_it("CRON", getpid(), "ERROR", "Failed to translate security class file", errno);
return 0;
}
bit = string_to_av_perm(tclass, "entrypoint");
if (!bit) {
log_it("CRON", getpid(), "ERROR", "Failed to translate av perm entrypoint", errno);
return 0;
}
/*
* Since crontab files are not directly executed,
* crond must ensure that the crontab file has
* a context that is appropriate for the context of
* the user cron job. It performs an entrypoint
* permission check for this purpose.
*/
retval = security_compute_av(scontext, file_context,
tclass, bit, &avd);
if (retval || ((bit & avd.allowed) != bit))
return 0;
return 1;
}
#endif
#ifdef WITH_SELINUX
static int cron_authorize_range(security_context_t scontext,
security_context_t ucontext) {
struct av_decision avd;
int retval;
security_class_t tclass;
access_vector_t bit;
tclass = string_to_security_class("context");
if (!tclass) {
log_it("CRON", getpid(), "ERROR", "Failed to translate security class context", errno);
return 0;
}
bit = string_to_av_perm(tclass, "contains");
if (!bit) {
log_it("CRON", getpid(), "ERROR", "Failed to translate av perm contains", errno);
return 0;
}
/*
* Since crontab files are not directly executed,
* so crond must ensure that any user specified range
* falls within the seusers-specified range for that Linux user.
*/
retval = security_compute_av(scontext, ucontext,
tclass, bit, &avd);
if (retval || ((bit & avd.allowed) != bit))
return 0;
return 1;
}
#endif
#if WITH_SELINUX
/* always uses u->scontext as the default process context, then changes the
level, and returns it in ucontextp (or NULL otherwise) */
static int
cron_get_job_range(user * u, security_context_t * ucontextp, char **jobenv) {
char *range;
if (is_selinux_enabled() <= 0)
return 0;
if (ucontextp == NULL)
return -1;
*ucontextp = NULL;
if ((range = env_get("MLS_LEVEL", jobenv)) != NULL) {
context_t ccon;
if (!(ccon = context_new(u->scontext))) {
log_it(u->name, getpid(), "context_new FAILED for MLS_LEVEL",
range, 0);
context_free(ccon);
return -1;
}
if (context_range_set(ccon, range)) {
log_it(u->name, getpid(),
"context_range_set FAILED for MLS_LEVEL", range, 0);
context_free(ccon);
return -1;
}
if (!(*ucontextp = context_str(ccon))) {
log_it(u->name, getpid(), "context_str FAILED for MLS_LEVEL",
range, 0);
context_free(ccon);
return -1;
}
if (!(*ucontextp = strdup(*ucontextp))) {
log_it(u->name, getpid(), "strdup FAILED for MLS_LEVEL", range, 0);
return -1;
}
context_free(ccon);
}
else if (!u->scontext) {
/* cron_change_selinux_range() deals with this */
return 0;
}
else if (!(*ucontextp = strdup(u->scontext))) {
log_it(u->name, getpid(), "strdup FAILED for MLS_LEVEL", range, 0);
return -1;
}
return 0;
}
#endif
#ifdef WITH_SELINUX
static int cron_change_selinux_range(user * u, security_context_t ucontext) {
char *msg = NULL;
if (is_selinux_enabled() <= 0)
return 0;
if (u->scontext == NULL) {
if (security_getenforce() > 0) {
log_it(u->name, getpid(), "NULL security context for user", "", 0);
return -1;
}
else {
log_it(u->name, getpid(),
"NULL security context for user, "
"but SELinux in permissive mode, continuing", "", 0);
return 0;
}
}
if (!ucontext || strcmp(u->scontext, ucontext)) {
if (!cron_authorize_range(u->scontext, ucontext)) {
if (security_getenforce() > 0) {
# ifdef WITH_AUDIT
if (asprintf(&msg,
"cron: Unauthorized MLS range acct=%s new_scontext=%s old_scontext=%s",
u->name, (char *) ucontext, u->scontext) >= 0) {
int audit_fd = audit_open();
audit_log_user_message(audit_fd, AUDIT_USER_ROLE_CHANGE,
msg, NULL, NULL, NULL, 0);
close(audit_fd);
free(msg);
}
# endif
if (asprintf
(&msg, "Unauthorized range in %s for user range in %s",
(char *) ucontext, u->scontext) >= 0) {
log_it(u->name, getpid(), "ERROR", msg, 0);
free(msg);
}
return -1;
}
else {
if (asprintf
(&msg,
"Unauthorized range in %s for user range in %s,"
" but SELinux in permissive mod, continuing",
(char *) ucontext, u->scontext) >= 0) {
log_it(u->name, getpid(), "WARNING", msg, 0);
free(msg);
}
}
}
}
if (setexeccon(ucontext) < 0) {
if (security_getenforce() > 0) {
if (asprintf
(&msg, "Could not set exec or keycreate context to %s for user",
(char *) ucontext) >= 0) {
log_it(u->name, getpid(), "ERROR", msg, 0);
free(msg);
}
return -1;
}
else {
if (asprintf
(&msg,
"Could not set exec context to %s for user,"
" but SELinux in permissive mode, continuing",
(char *) ucontext) >= 0) {
log_it(u->name, getpid(), "WARNING", msg, 0);
free(msg);
}
return 0;
}
}
return 0;
}
#endif
#ifdef WITH_SELINUX
int
get_security_context(const char *name, int crontab_fd,
security_context_t * rcontext, const char *tabname) {
security_context_t scontext = NULL;
security_context_t file_context = NULL;
security_context_t rawcontext=NULL;
context_t current_context = NULL;
int retval;
char *current_context_str = NULL;
char *seuser = NULL;
char *level = NULL;
*rcontext = NULL;
if (is_selinux_enabled() <= 0)
return 0;
if (name != NULL) {
if (getseuserbyname(name, &seuser, &level) < 0) {
log_it(name, getpid(), "getseuserbyname FAILED", name, 0);
return security_getenforce() > 0 ? -1 : 0;
}
retval = get_default_context_with_level(seuser, level, NULL, &scontext);
}
else {
const char *current_user, *current_role;
if (getcon(&current_context_str) < 0) {
log_it(name, getpid(), "getcon FAILED", "", 0);
return (security_getenforce() > 0);
}
current_context = context_new(current_context_str);
if (current_context == NULL) {
log_it(name, getpid(), "context_new FAILED", current_context_str, 0);
freecon(current_context_str);
return (security_getenforce() > 0);
}
current_user = context_user_get(current_context);
current_role = context_role_get(current_context);
retval = get_default_context_with_rolelevel(current_user, current_role, level, NULL, &scontext);
freecon(current_context_str);
context_free(current_context);
}
if (selinux_trans_to_raw_context(scontext, &rawcontext) == 0) {
freecon(scontext);
scontext = rawcontext;
}
free(seuser);
free(level);
if (retval) {
if (security_getenforce() > 0) {
log_it(name, getpid(), "No SELinux security context", tabname, 0);
return -1;
}
else {
log_it(name, getpid(),
"No security context but SELinux in permissive mode, continuing",
tabname, 0);
return 0;
}
}
if (fgetfilecon(crontab_fd, &file_context) < OK) {
if (security_getenforce() > 0) {
log_it(name, getpid(), "getfilecon FAILED", tabname, 0);
freecon(scontext);
return -1;
}
else {
log_it(name, getpid(),
"getfilecon FAILED but SELinux in permissive mode, continuing",
tabname, 0);
*rcontext = scontext;
return 0;
}
}
if (!cron_authorize_context(scontext, file_context)) {
char *msg=NULL;
if (asprintf(&msg,
"Unauthorized SELinux context=%s file_context=%s", (char *) scontext, file_context) >= 0) {
log_it(name, getpid(), msg, tabname, 0);
free(msg);
} else {
log_it(name, getpid(), "Unauthorized SELinux context", tabname, 0);
}
freecon(scontext);
freecon(file_context);
if (security_getenforce() > 0) {
return -1;
}
else {
log_it(name, getpid(),
"SELinux in permissive mode, continuing",
tabname, 0);
return 0;
}
}
freecon(file_context);
*rcontext = scontext;
return 0;
}
#endif
#ifdef WITH_SELINUX
void free_security_context(security_context_t * scontext) {
if (*scontext != NULL) {
freecon(*scontext);
*scontext = NULL;
}
}
#endif
#ifdef WITH_SELINUX
int crontab_security_access(void) {
int selinux_check_passwd_access = -1;
if (is_selinux_enabled() > 0) {
security_context_t user_context;
if (getprevcon_raw(&user_context) == 0) {
security_class_t passwd_class;
access_vector_t crontab_bit;
struct av_decision avd;
int retval = 0;
passwd_class = string_to_security_class("passwd");
if (passwd_class == 0) {
fprintf(stderr, "Security class \"passwd\" is not defined in the SELinux policy.\n");
retval = -1;
}
if (retval == 0) {
crontab_bit = string_to_av_perm(passwd_class, "crontab");
if (crontab_bit == 0) {
fprintf(stderr, "Security av permission \"crontab\" is not defined in the SELinux policy.\n");
retval = -1;
}
}
if (retval == 0)
retval = security_compute_av_raw(user_context,
user_context, passwd_class,
crontab_bit, &avd);
if ((retval == 0) && ((crontab_bit & avd.allowed) == crontab_bit)) {
selinux_check_passwd_access = 0;
}
freecon(user_context);
}
if (selinux_check_passwd_access != 0 && security_getenforce() == 0)
selinux_check_passwd_access = 0;
return selinux_check_passwd_access;
}
return 0;
}
#endif
/* Build up the job environment from the PAM environment plus the
* crontab environment
*/
static char **build_env(char **cronenv) {
char **jobenv;
#ifdef WITH_PAM
char *cronvar;
int count = 0;
if (pamh == NULL || (jobenv=pam_getenvlist(pamh)) == NULL) {
#endif
jobenv = env_copy(cronenv);
if (jobenv == NULL)
log_it("CRON", getpid(),
"ERROR", "Initialization of cron environment variables failed", 0);
return jobenv;
#ifdef WITH_PAM
}
/* Now add the cron environment variables. Since env_set()
* overwrites existing variables, this will let cron's
* environment settings override pam's */
while ((cronvar = cronenv[count++])) {
if (!(jobenv = env_set(jobenv, cronvar))) {
log_it("CRON", getpid(),
"Setting Cron environment variable failed", cronvar, 0);
return NULL;
}
}
return jobenv;
#endif
}
/* int in_file(const char *string, FILE *file, int error)
* return TRUE if one of the lines in file matches string exactly,
* FALSE if no lines match, and error on error.
*/
static int in_file(const char *string, FILE * file, int error) {
char line[MAX_TEMPSTR];
char *endp;
if (fseek(file, 0L, SEEK_SET))
return (error);
while (fgets(line, MAX_TEMPSTR, file)) {
if (line[0] != '\0') {
endp = &line[strlen(line) - 1];
if (*endp != '\n')
return (error);
*endp = '\0';
if (0 == strcmp(line, string))
return (TRUE);
}
}
if (ferror(file))
return (error);
return (FALSE);
}
/* int allowed(const char *username, const char *allow_file, const char *deny_file)
* returns TRUE if (allow_file exists and user is listed)
* or (deny_file exists and user is NOT listed).
* root is always allowed.
*/
int allowed(const char *username, const char *allow_file,
const char *deny_file) {
FILE *fp;
int isallowed;
char buf[128];
if (getuid() == 0)
return TRUE;
isallowed = FALSE;
if ((fp = fopen(allow_file, "r")) != NULL) {
isallowed = in_file(username, fp, FALSE);
fclose(fp);
if ((getuid() == 0) && (!isallowed)) {
snprintf(buf, sizeof (buf),
"root used -u for user %s not in cron.allow", username);
log_it("crontab", getpid(), "warning", buf, 0);
isallowed = TRUE;
}
}
else if ((fp = fopen(deny_file, "r")) != NULL) {
isallowed = !in_file(username, fp, FALSE);
fclose(fp);
if ((getuid() == 0) && (!isallowed)) {
snprintf(buf, sizeof (buf),
"root used -u for user %s in cron.deny", username);
log_it("crontab", getpid(), "warning", buf, 0);
isallowed = TRUE;
}
}
#ifdef WITH_AUDIT
if (isallowed == FALSE) {
int audit_fd = audit_open();
audit_log_user_message(audit_fd, AUDIT_USER_START, "cron deny",
NULL, NULL, NULL, 0);
close(audit_fd);
}
#endif
return (isallowed);
}

93
src/structs.h Normal file
View File

@@ -0,0 +1,93 @@
/*
* $Id: structs.h,v 1.7 2004/01/23 18:56:43 vixie Exp $
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef CRONIE_STRUCTS_H
#define CRONIE_STRUCTS_H
#include <time.h>
#include <sys/types.h>
#ifdef WITH_SELINUX
#include <selinux/selinux.h>
#endif
#include "macros.h"
#include "bitstring.h"
typedef struct _entry {
struct _entry *next;
struct passwd *pwd;
char **envp;
char *cmd;
bitstr_t bit_decl(minute, MINUTE_COUNT);
bitstr_t bit_decl(hour, HOUR_COUNT);
bitstr_t bit_decl(dom, DOM_COUNT);
bitstr_t bit_decl(month, MONTH_COUNT);
bitstr_t bit_decl(dow, DOW_COUNT);
int flags;
int delay;
#define MIN_STAR 0x01
#define HR_STAR 0x02
#define DOM_STAR 0x04
#define DOW_STAR 0x08
#define WHEN_REBOOT 0x10
#define DONT_LOG 0x20
#define MAIL_WHEN_ERR 0x40
} entry;
/* the crontab database will be a list of the
* following structure, one element per user
* plus one for the system.
*
* These are the crontabs.
*/
#ifndef WITH_SELINUX
#define security_context_t unsigned
#endif
typedef struct _user {
struct _user *next, *prev; /* links */
char *name;
char *tabname; /* /etc/cron.d/ file name or NULL */
time_t mtime; /* last modtime of crontab */
entry *crontab; /* this person's crontab */
security_context_t scontext; /* SELinux security context */
int system; /* is it a system crontab */
} user;
typedef struct _orphan {
struct _orphan *next; /* link */
char *uname;
char *fname;
char *tabname;
} orphan;
typedef struct _cron_db {
user *head, *tail; /* links */
time_t mtime; /* last modtime on spooldir */
#ifdef WITH_INOTIFY
int ifd;
#endif
} cron_db;
/* in the C tradition, we only create
* variables for the main program, just
* extern them elsewhere.
*/
#endif /* CRONIE_STRUCTS_H */

179
src/user.c Normal file
View File

@@ -0,0 +1,179 @@
/* Copyright 1988,1990,1993,1994 by Paul Vixie
* All rights reserved
*/
/*
* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* vix 26jan87 [log is in RCS file]
*/
#include "config.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "funcs.h"
#include "globals.h"
static const char *FileName;
static void
log_error (const char *msg)
{
log_it ("CRON", getpid (), msg, FileName, 0);
}
void
free_user (user * u) {
entry *e, *ne;
if (!u) {
return;
}
free(u->name);
free(u->tabname);
for (e = u->crontab; e != NULL; e = ne) {
ne = e->next;
free_entry(e);
}
#ifdef WITH_SELINUX
free_security_context(&(u->scontext));
#endif
free(u);
}
user *
load_user (int crontab_fd, struct passwd *pw, const char *uname,
const char *fname, const char *tabname) {
char envstr[MAX_ENVSTR];
FILE *file;
user *u;
entry *e;
int status = TRUE, save_errno = 0;
char **envp = NULL, **tenvp;
int envs = 0, entries = 0;
if (!(file = fdopen(crontab_fd, "r"))) {
save_errno = errno;
log_it(uname, getpid (), "FAILED", "fdopen on crontab_fd in load_user",
save_errno);
close(crontab_fd);
return (NULL);
}
Debug(DPARS, ("load_user()\n"));
/* file is open. build user entry, then read the crontab file.
*/
if ((u = (user *) malloc (sizeof (user))) == NULL) {
save_errno = errno;
goto done;
}
memset(u, 0, sizeof(*u));
if (((u->name = strdup(fname)) == NULL)
|| ((u->tabname = strdup(tabname)) == NULL)) {
save_errno = errno;
goto done;
}
u->system = pw == NULL;
/* init environment. this will be copied/augmented for each entry.
*/
if ((envp = env_init()) == NULL) {
save_errno = errno;
goto done;
}
if (env_set_from_environ(&envp) == FALSE) {
save_errno = errno;
goto done;
}
#ifdef WITH_SELINUX
if (get_security_context(pw == NULL ? NULL : uname,
crontab_fd, &u->scontext, tabname) != 0) {
goto done;
}
#endif
/* load the crontab
*/
while (status >= OK) {
if (!skip_comments(file) && !u->system) {
log_error("too many garbage characters");
status = TRUE;
break;
}
status = load_env (envstr, file);
switch (status) {
case ERR:
/* If envstr has content, we reached EOF
* without a newline, and the line will be
* ignored.
*/
if (envstr[0] != '\0') {
FileName = tabname;
log_error("missing newline before EOF");
}
break;
case FALSE:
++entries;
if (!u->system && entries > MAX_USER_ENTRIES) {
log_error("too many entries");
status = TRUE;
goto done;
}
FileName = tabname;
e = load_entry(file, log_error, pw, envp);
if (e) {
e->next = u->crontab;
u->crontab = e;
}
break;
case TRUE:
++envs;
if (!u->system && envs > MAX_USER_ENVS) {
log_error("too many environment variables");
goto done;
}
if ((tenvp = env_set (envp, envstr)) == NULL) {
save_errno = errno;
goto done;
}
envp = tenvp;
break;
}
}
done:
if (status == TRUE) {
log_it(uname, getpid(), "FAILED", "loading cron table",
save_errno);
free_user(u);
u = NULL;
}
if (envp)
env_free(envp);
fclose(file);
Debug(DPARS, ("...load_user() done\n"));
errno = save_errno;
return (u);
}