Merge system-wide Vixie cron updates.
I don't believe that anyone should be running system-wide cron processes these days (the attack surface is rather large), but should use separate per-user or per-service mcron daemon processes. But mcron is advertised as a drop-in Vixie replacement, so we should do what we can to make it safe in this use case. I've performed a basic vetting of the changes against vandalism, but haven't verified the correctness of the code or done any checking; the changes are being accepted on the basis that almost anything is an improvement on what currently exists.
This commit is contained in:
commit
0fe4d2cc95
8 changed files with 333 additions and 154 deletions
53
Makefile.am
53
Makefile.am
|
|
@ -27,10 +27,17 @@ noinst_SCRIPTS =
|
|||
if MULTI_USER
|
||||
bin_SCRIPTS += bin/crontab
|
||||
sbin_SCRIPTS = bin/cron
|
||||
libexec_SCRIPTS = bin/crontab-access-real
|
||||
sbin_PROGRAMS = bin/crontab-access
|
||||
else
|
||||
noinst_SCRIPTS += bin/cron bin/crontab
|
||||
noinst_SCRIPTS += bin/cron bin/crontab bin/crontab-access-real
|
||||
noinst_PROGRAMS = bin/crontab-access
|
||||
endif
|
||||
|
||||
# The dynamic linker should detect that it's being run for a setuid program,
|
||||
# but we take no chances.
|
||||
bin_crontab_access_LDFLAGS = -static
|
||||
|
||||
# wrapper to be used in the build environment and for running tests.
|
||||
noinst_SCRIPTS += pre-inst-env
|
||||
|
||||
|
|
@ -68,6 +75,7 @@ pkgscriptdir = $(pkgmoduledir)/scripts
|
|||
dist_pkgscript_DATA = \
|
||||
src/mcron/scripts/cron.scm \
|
||||
src/mcron/scripts/crontab.scm \
|
||||
src/mcron/scripts/crontab-access.scm \
|
||||
src/mcron/scripts/mcron.scm
|
||||
|
||||
pkgscriptgodir = $(pkgmodulegodir)/scripts
|
||||
|
|
@ -77,7 +85,13 @@ compiled_modules = \
|
|||
$(pkgmodulego_DATA) \
|
||||
$(pkgscriptgo_DATA)
|
||||
|
||||
CLEANFILES = $(compiled_modules) bin/crontab bin/cron bin/mcron
|
||||
CLEANFILES = $(compiled_modules) \
|
||||
bin/crontab \
|
||||
bin/crontab-access \
|
||||
src/crontab-access.c \
|
||||
bin/crontab-access-real \
|
||||
bin/cron \
|
||||
bin/mcron
|
||||
DISTCLEANFILES = src/mcron/config.scm
|
||||
|
||||
# Unset 'GUILE_LOAD_COMPILED_PATH' altogether while compiling. Otherwise, if
|
||||
|
|
@ -100,10 +114,9 @@ DISTCLEANFILES = src/mcron/config.scm
|
|||
--warn=format --warn=unbound-variable --warn=arity-mismatch \
|
||||
--target="$(host)" --output="$@" "$<" $(devnull_verbose)
|
||||
|
||||
|
||||
bin/% : src/%.in Makefile
|
||||
$(AM_V_GEN)$(MKDIR_P) bin ; \
|
||||
sed -e 's,%PREFIX%,${prefix},g' \
|
||||
do_subst = sed -e 's,%PREFIX%,${prefix},g' \
|
||||
-e 's,%sbindir%,${sbindir},g' \
|
||||
-e 's,%libexecdir%,${libexecdir},g' \
|
||||
-e 's,%modsrcdir%,${guilesitedir},g' \
|
||||
-e 's,%modbuilddir%,${guilesitegodir},g' \
|
||||
-e 's,%localstatedir%,${localstatedir},g' \
|
||||
|
|
@ -114,8 +127,17 @@ bin/% : src/%.in Makefile
|
|||
-e 's,%PACKAGE_BUGREPORT%,@PACKAGE_BUGREPORT@,g' \
|
||||
-e 's,%PACKAGE_NAME%,@PACKAGE_NAME@,g' \
|
||||
-e 's,%PACKAGE_URL%,@PACKAGE_URL@,g' \
|
||||
-e 's,%GUILE%,$(GUILE),g' \
|
||||
$< > $@ ; \
|
||||
-e 's,%GUILE%,$(GUILE),g'
|
||||
|
||||
src/mcron/config.scm: src/mcron/config.scm.in Makefile
|
||||
$(AM_V_GEN)$(do_subst) $< > $@
|
||||
|
||||
src/crontab-access.c: src/crontab-access.c.in Makefile
|
||||
$(AM_V_GEN)$(do_subst) $< > $@
|
||||
|
||||
bin/% : src/%.in Makefile
|
||||
$(AM_V_GEN)$(MKDIR_P) bin ; \
|
||||
$(do_subst) $< > $@ ; \
|
||||
chmod a+x $@
|
||||
|
||||
|
||||
|
|
@ -153,6 +175,8 @@ EXTRA_DIST = \
|
|||
HACKING \
|
||||
src/cron.in \
|
||||
src/crontab.in \
|
||||
src/crontab-access-real.in \
|
||||
src/crontab-access.c.in \
|
||||
src/mcron.in \
|
||||
tests/init.sh \
|
||||
$(TESTS)
|
||||
|
|
@ -166,10 +190,10 @@ transform_exe = s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/
|
|||
|
||||
install-exec-hook:
|
||||
if MULTI_USER
|
||||
tcrontab=`echo crontab | sed '$(transform_exe)'`; \
|
||||
chmod u+s $(DESTDIR)$(bindir)/$${tcrontab}
|
||||
tcron=`echo cron | sed '$(transform_exe)'`; \
|
||||
chmod u+s $(DESTDIR)$(sbindir)/$${tcron}
|
||||
tcrontab=`echo crontab | sed '$(transform_exe)'`;
|
||||
tcrontab_access=`echo crontab-access | sed '$(transform_exe)'`; \
|
||||
chmod u+s $(DESTDIR)$(sbindir)/$${tcrontab_access}
|
||||
tcron=`echo cron | sed '$(transform_exe)'`;
|
||||
endif
|
||||
tmcron=`echo mcron | sed '$(transform_exe)'`;
|
||||
|
||||
|
|
@ -178,8 +202,9 @@ installcheck-local:
|
|||
tmcron=`echo mcron | sed '$(transform_exe)'`; \
|
||||
test -e $(DESTDIR)$(bindir)/$${tmcron}
|
||||
if MULTI_USER
|
||||
tcrontab=`echo crontab | sed '$(transform_exe)'`; \
|
||||
test -u $(DESTDIR)$(bindir)/$${tcrontab}
|
||||
tcrontab=`echo crontab | sed '$(transform_exe)'`;
|
||||
tcrontab_access=`echo crontab | sed '$(transform_exe)'`; \
|
||||
test -u $(DESTDIR)$(bindir)/$${tcrontab_access}
|
||||
tcron=`echo cron | sed '$(transform_exe)'`; \
|
||||
test -e $(DESTDIR)$(sbindir)/$${tcron}
|
||||
else !MULTI_USER
|
||||
|
|
|
|||
10
configure.ac
10
configure.ac
|
|
@ -65,6 +65,14 @@ AC_ARG_ENABLE([multi-user],
|
|||
[Don't Install legacy cron and crontab programs])],
|
||||
[enable_multi_user="$enableval"],
|
||||
[enable_multi_user="yes"])
|
||||
|
||||
dnl Not possible to run this conditionally?
|
||||
AC_PROG_CC
|
||||
dnl AS_IF([test "x$enable_multi_user" = xyes],
|
||||
dnl [# Need a C compiler to compile setuid wrapper
|
||||
dnl AC_PROG_CC]
|
||||
dnl fi
|
||||
|
||||
AM_CONDITIONAL([MULTI_USER], [test "x$enable_multi_user" = xyes])
|
||||
|
||||
# Configure the various files that mcron uses at runtime.
|
||||
|
|
@ -127,5 +135,5 @@ AC_CONFIG_FILES([pre-inst-env:build-aux/pre-inst-env.in],
|
|||
[chmod +x pre-inst-env])
|
||||
AC_CONFIG_FILES([doc/config.texi
|
||||
Makefile
|
||||
src/mcron/config.scm])
|
||||
src/mcron/config.scm.in])
|
||||
AC_OUTPUT
|
||||
|
|
|
|||
45
src/crontab-access-real.in
Normal file
45
src/crontab-access-real.in
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#!%GUILE% --no-auto-compile
|
||||
-*- scheme -*-
|
||||
!#
|
||||
|
||||
;;;; crontab -- run jobs at scheduled times
|
||||
;;; Copyright © 2003, 2020 Dale Mellor <mcron-lsfnyl@rdmp.org>
|
||||
;;; Copyright © 2015, 2016, 2018 Mathieu Lirzin <mthl@gnu.org>
|
||||
;;;
|
||||
;;; This file is part of GNU Mcron.
|
||||
;;;
|
||||
;;; GNU Mcron 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 3 of the License, or
|
||||
;;; (at your option) any later version.
|
||||
;;;
|
||||
;;; GNU Mcron 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 GNU Mcron. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
(unless (getenv "MCRON_UNINSTALLED")
|
||||
(set! %load-path (cons "%modsrcdir%" %load-path))
|
||||
(set! %load-compiled-path (cons "%modbuilddir%" %load-compiled-path)))
|
||||
|
||||
(use-modules (mcron scripts crontab)
|
||||
(mcron command-line-processor))
|
||||
|
||||
(process-command-line (command-line)
|
||||
application "crontab"
|
||||
version "%VERSION%"
|
||||
usage "[-u user] { -R | -l | -r }"
|
||||
help-preamble "the default operation is to list."
|
||||
option (--user= -u "the user whose files are to be manipulated")
|
||||
option (--replace -R "replace this userʼs crontab via stdin")
|
||||
option (--list -l "list this userʼs crontab")
|
||||
option (--remove -r "delete the userʼs crontab")
|
||||
bug-address "%PACKAGE_BUGREPORT%"
|
||||
copyright "2003, 2016, 2020 Free Software Foundation, Inc."
|
||||
license GPLv3)
|
||||
|
||||
((@ (mcron scripts crontab-access) main) --user --replace --list --remove)
|
||||
10
src/crontab-access.c.in
Normal file
10
src/crontab-access.c.in
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char *envp = NULL;
|
||||
execve("%libexecdir%/crontab-access-real",
|
||||
argv, &envp);
|
||||
/* Should not get here! */
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
(define-public config-package-url "@PACKAGE_URL@")
|
||||
(define-public config-sendmail "@SENDMAIL@")
|
||||
|
||||
(define-public config-sbin-dir "%sbindir%")
|
||||
(define-public config-spool-dir "@CONFIG_SPOOL_DIR@")
|
||||
(define-public config-socket-file "@CONFIG_SOCKET_FILE@")
|
||||
(define-public config-allow-file "@CONFIG_ALLOW_FILE@")
|
||||
|
|
@ -147,7 +147,7 @@ option.\n")
|
|||
(delete-file config-socket-file))
|
||||
noop)
|
||||
(exit EXIT_FAILURE))))
|
||||
'(SIGTERM SIGINT SIGQUIT SIGHUP))
|
||||
(list SIGTERM SIGINT SIGQUIT SIGHUP))
|
||||
|
||||
;; We can now write the PID file.
|
||||
(with-output-to-file config-pid-file
|
||||
|
|
|
|||
121
src/mcron/scripts/crontab-access.scm
Normal file
121
src/mcron/scripts/crontab-access.scm
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
;;;; crontab -- edit user's cron tabs
|
||||
;;; Copyright © 2003, 2004 Dale Mellor <>
|
||||
;;; Copyright © 2016 Mathieu Lirzin <mthl@gnu.org>
|
||||
;;;
|
||||
;;; This file is part of GNU Mcron.
|
||||
;;;
|
||||
;;; GNU Mcron 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 3 of the License, or
|
||||
;;; (at your option) any later version.
|
||||
;;;
|
||||
;;; GNU Mcron 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 GNU Mcron. If not, see <http://www.gnu.org/licenses/>.
|
||||
(define-module (mcron scripts crontab-access)
|
||||
#:use-module (ice-9 rdelim)
|
||||
#:use-module (mcron config)
|
||||
#:use-module (mcron utils)
|
||||
#:use-module (mcron vixie-specification)
|
||||
#:export (main))
|
||||
|
||||
(define (hit-server user-name)
|
||||
"Tell the running cron daemon that the user corresponding to
|
||||
USER-NAME has modified his crontab. USER-NAME is written to the
|
||||
'/var/cron/socket' UNIX socket."
|
||||
(catch #t
|
||||
(lambda ()
|
||||
(let ((socket (socket AF_UNIX SOCK_STREAM 0)))
|
||||
(connect socket AF_UNIX config-socket-file)
|
||||
(display user-name socket)
|
||||
(close socket)))
|
||||
(lambda (key . args)
|
||||
(display "Warning: a cron daemon is not running.\n"))))
|
||||
|
||||
(define (in-access-file? file name)
|
||||
"Scan FILE which should contain one user name per line (such as
|
||||
'/var/cron/allow' and '/var/cron/deny'). Return #t if NAME is in there, and
|
||||
#f otherwise. If FILE cannot be opened, a value that is neither #t nor #f
|
||||
is returned."
|
||||
(catch #t
|
||||
(lambda ()
|
||||
(with-input-from-file file
|
||||
(lambda ()
|
||||
(let loop ((input (read-line)))
|
||||
(cond ((eof-object? input)
|
||||
#f)
|
||||
((string=? input name)
|
||||
#t)
|
||||
(else
|
||||
(loop (read-line))))))))
|
||||
(const '())))
|
||||
|
||||
(define (main --user --replace --list --remove)
|
||||
(when config-debug (debug-enable 'backtrace))
|
||||
(let ((crontab-real-user
|
||||
;; This program should have been installed SUID root. Here we get
|
||||
;; the passwd entry for the real user who is running this program.
|
||||
(passwd:name (getpw (getuid)))))
|
||||
|
||||
;; If the real user is not allowed to use crontab due to the
|
||||
;; /var/cron/allow and/or /var/cron/deny files, bomb out now.
|
||||
(if (or (eq? (in-access-file? config-allow-file crontab-real-user) #f)
|
||||
(eq? (in-access-file? config-deny-file crontab-real-user) #t))
|
||||
(mcron-error 6 "Access denied by system operator."))
|
||||
|
||||
;; Check that no more than one of the mutually exclusive options are
|
||||
;; being used.
|
||||
(when (< 1 (+ (if --list 1 0) (if --remove 1 0) (if --replace 1 0)))
|
||||
(mcron-error 7 "Only one of options -l, -r or -R can be used."))
|
||||
|
||||
;; Check that a non-root user is trying to read someone else's files.
|
||||
(when (and (not (zero? (getuid))) --user)
|
||||
(mcron-error 8 "Only root can use the -u option."))
|
||||
|
||||
;; Crontabs being written should not have global or group access.
|
||||
(umask #o077)
|
||||
|
||||
(letrec* ( ;; Iff the --user option is given, the crontab-user may be
|
||||
;; different from the real user.
|
||||
(crontab-user (or --user crontab-real-user))
|
||||
;; So now we know which crontab file we will be manipulating.
|
||||
(crontab-file
|
||||
(string-append config-spool-dir "/" crontab-user)))
|
||||
;; There are three possible actions: list, remove, and replace (via
|
||||
;; stdin).
|
||||
(cond
|
||||
;; In the remove personality we simply make an effort to delete the
|
||||
;; crontab and wake the daemon. No worries if this fails.
|
||||
(--remove (catch #t (λ () (delete-file crontab-file)
|
||||
(hit-server crontab-user))
|
||||
noop))
|
||||
|
||||
;; Read crontab from stdin, verify it, replace it, wake daemon.
|
||||
(--replace
|
||||
(let ((input-string (read-string)))
|
||||
(catch-mcron-error
|
||||
(read-vixie-port (open-input-string input-string))
|
||||
(unless (file-exists? config-spool-dir)
|
||||
(mkdir config-spool-dir #o700))
|
||||
(with-output-to-file crontab-file
|
||||
(λ () (display input-string))))
|
||||
(hit-server crontab-user)))
|
||||
|
||||
;; In the list personality, we simply open the crontab and copy it
|
||||
;; character-by-character to the standard output. If anything goes
|
||||
;; wrong, it can only mean that this user does not have a crontab
|
||||
;; file.
|
||||
(else ;; --list or no action specified
|
||||
(catch #t
|
||||
(λ ()
|
||||
(with-input-from-file crontab-file
|
||||
(λ ()
|
||||
(do ((input (read-char) (read-char)))
|
||||
((eof-object? input))
|
||||
(display input)))))
|
||||
(λ (key . args)
|
||||
(mcron-error 17 "No crontab for " crontab-user " exists.\n"))))))))
|
||||
|
|
@ -19,26 +19,12 @@
|
|||
|
||||
(define-module (mcron scripts crontab)
|
||||
#:use-module (ice-9 rdelim)
|
||||
#:use-module (srfi srfi-1)
|
||||
#:use-module (mcron config)
|
||||
#:use-module (mcron utils)
|
||||
#:use-module (mcron vixie-specification)
|
||||
#:export (main))
|
||||
|
||||
(define (hit-server user-name)
|
||||
"Tell the running cron daemon that the user corresponding to
|
||||
USER-NAME has modified his crontab. USER-NAME is written to the
|
||||
'/var/cron/socket' UNIX socket."
|
||||
(catch #t
|
||||
(lambda ()
|
||||
(let ((socket (socket AF_UNIX SOCK_STREAM 0)))
|
||||
(connect socket AF_UNIX config-socket-file)
|
||||
(display user-name socket)
|
||||
(close socket)))
|
||||
(lambda (key . args)
|
||||
(display "Warning: a cron daemon is not running.\n"))))
|
||||
|
||||
|
||||
|
||||
;; Display the prompt and wait for user to type his choice. Return #t if the
|
||||
;; answer begins with 'y' or 'Y', return #f if it begins with 'n' or 'N',
|
||||
;; otherwise ask again.
|
||||
|
|
@ -56,23 +42,6 @@ USER-NAME has modified his crontab. USER-NAME is written to the
|
|||
|
||||
|
||||
|
||||
(define (in-access-file? file name)
|
||||
"Scan FILE which should contain one user name per line (such as
|
||||
'/var/cron/allow' and '/var/cron/deny'). Return #t if NAME is in there, and
|
||||
#f otherwise. if FILE cannot be opened, a error is signaled."
|
||||
(catch #t
|
||||
(lambda ()
|
||||
(with-input-from-file file
|
||||
(lambda ()
|
||||
(let loop ((input (read-line)))
|
||||
(cond ((eof-object? input)
|
||||
#f)
|
||||
((string=? input name)
|
||||
#t)
|
||||
(else
|
||||
(loop (read-line))))))))
|
||||
(const '())))
|
||||
|
||||
|
||||
;;;
|
||||
;;; Entry point.
|
||||
|
|
@ -80,117 +49,117 @@ USER-NAME has modified his crontab. USER-NAME is written to the
|
|||
|
||||
(define (main --user --edit --list --remove files)
|
||||
(when config-debug (debug-enable 'backtrace))
|
||||
(let ((crontab-real-user
|
||||
;; This program should have been installed SUID root. Here we get
|
||||
;; the passwd entry for the real user who is running this program.
|
||||
(passwd:name (getpw (getuid)))))
|
||||
;; Check that no more than one of the mutually exclusive options are
|
||||
;; being used.
|
||||
(when (< 1 (+ (if --edit 1 0) (if --list 1 0) (if --remove 1 0)))
|
||||
(mcron-error 7 "Only one of options -e, -l or -r can be used."))
|
||||
|
||||
;; If the real user is not allowed to use crontab due to the
|
||||
;; /var/cron/allow and/or /var/cron/deny files, bomb out now.
|
||||
(if (or (eq? (in-access-file? config-allow-file crontab-real-user) #f)
|
||||
(eq? (in-access-file? config-deny-file crontab-real-user) #t))
|
||||
(mcron-error 6 "Access denied by system operator."))
|
||||
;; Check that a non-root user is trying to read someone else's files.
|
||||
;; This will be enforced in the setuid helper 'crontab-access', but good
|
||||
;; to let the user know early.
|
||||
(when (and (not (zero? (getuid))) --user)
|
||||
(mcron-error 8 "Only root can use the -u option."))
|
||||
|
||||
;; Check that no more than one of the mutually exclusive options are
|
||||
;; being used.
|
||||
(when (< 1 (+ (if --edit 1 0) (if --list 1 0) (if --remove 1 0)))
|
||||
(mcron-error 7 "Only one of options -e, -l or -r can be used."))
|
||||
;; Crontabs being edited should not be global or group-readable.
|
||||
(umask #o077)
|
||||
|
||||
;; Check that a non-root user is trying to read someone else's files.
|
||||
(when (and (not (zero? (getuid))) --user)
|
||||
(mcron-error 8 "Only root can use the -u option."))
|
||||
(let ((user-args (if --user (list "-u" --user) '())))
|
||||
(define (usable-crontab-access? filename)
|
||||
(and=> (stat filename #f)
|
||||
(λ (st)
|
||||
(or (zero? (getuid))
|
||||
(and (not (zero? (logand #o4000 (stat:mode st))))
|
||||
(zero? (stat:uid st))
|
||||
(access? filename X_OK))))))
|
||||
|
||||
(letrec* (;; Iff the --user option is given, the crontab-user may be
|
||||
;; different from the real user.
|
||||
(crontab-user (or --user crontab-real-user))
|
||||
;; So now we know which crontab file we will be manipulating.
|
||||
(crontab-file
|
||||
(string-append config-spool-dir "/" crontab-user)))
|
||||
;; There are four possible sub-personalities to the crontab
|
||||
;; personality: list, remove, edit and replace (when the user uses no
|
||||
;; options but supplies file names on the command line).
|
||||
(define crontab-access
|
||||
(find usable-crontab-access?
|
||||
(map (λ (f) (string-append f "/crontab-access"))
|
||||
(cons config-sbin-dir
|
||||
(or (and=> (getenv "PATH") parse-path)
|
||||
'())))))
|
||||
|
||||
(define (exec-crontab-access . args)
|
||||
(catch #t
|
||||
(λ ()
|
||||
(apply execlp crontab-access crontab-access
|
||||
(append user-args args))
|
||||
(mcron-error 18 "Couldn't execute `crontab-access'."))
|
||||
(λ args
|
||||
(apply mcron-error 18 "Couldn't execute `crontab-access'." args))))
|
||||
|
||||
(define (crontab-access-dup2 srcs dsts closes . args)
|
||||
(let ((pid (primitive-fork)))
|
||||
(cond
|
||||
;; In the list personality, we simply open the crontab and copy it
|
||||
;; character-by-character to the standard output. If anything goes
|
||||
;; wrong, it can only mean that this user does not have a crontab
|
||||
;; file.
|
||||
(--list
|
||||
((zero? pid)
|
||||
(for-each dup2 srcs dsts)
|
||||
(for-each close-fdes closes)
|
||||
(apply exec-crontab-access args))
|
||||
(else
|
||||
(waitpid pid)))))
|
||||
|
||||
(define (try-replace file)
|
||||
(call-with-input-file file
|
||||
(λ (port)
|
||||
(crontab-access-dup2 (list (fileno port)) '(0) '() "-R"))))
|
||||
|
||||
(unless crontab-access
|
||||
(mcron-error 18 "Couldn't find a usable `crontab-access'."))
|
||||
|
||||
;; There are four possible sub-personalities to the crontab
|
||||
;; personality: list, remove, edit and replace (when the user uses no
|
||||
;; options but supplies file names on the command line).
|
||||
(cond
|
||||
(--list (exec-crontab-access "-l"))
|
||||
(--remove (exec-crontab-access "-r"))
|
||||
|
||||
;; In the edit personality, we determine the name of a temporary file and
|
||||
;; an editor command, copy an existing crontab file (if it is there) to
|
||||
;; the temporary file, once the editor returns we try to replace any
|
||||
;; existing crontab file. If this fails, we give user a choice of
|
||||
;; editing the file again or quitting the program and losing all changes
|
||||
;; made.
|
||||
(--edit
|
||||
(let* ((template (string-append config-tmp-dir
|
||||
"/crontab."
|
||||
(number->string (getpid))
|
||||
".XXXXXX"))
|
||||
(temp-file (call-with-port (mkstemp template "w")
|
||||
(λ (port)
|
||||
(crontab-access-dup2 (list (fileno port)) '(1)
|
||||
'(2) "-l")
|
||||
(chmod port #o600)
|
||||
(port-filename port)))))
|
||||
(define (exit/cleanup status)
|
||||
(false-if-exception (delete-file temp-file))
|
||||
(primitive-exit status))
|
||||
|
||||
(let retry ()
|
||||
(catch #t
|
||||
(λ ()
|
||||
(with-input-from-file crontab-file
|
||||
(λ ()
|
||||
(do ((input (read-char) (read-char)))
|
||||
((eof-object? input))
|
||||
(display input)))))
|
||||
(λ (key . args)
|
||||
(display (string-append "No crontab for "
|
||||
crontab-user
|
||||
" exists.\n")))))
|
||||
|
||||
;; In the edit personality, we determine the name of a temporary file
|
||||
;; and an editor command, copy an existing crontab file (if it is
|
||||
;; there) to the temporary file, making sure the ownership is set so
|
||||
;; the real user can edit it; once the editor returns we try to read
|
||||
;; the file to check that it is parseable (but do nothing more with
|
||||
;; the configuration), and if it is okay (this program is still
|
||||
;; running!) we move the temporary file to the real crontab, wake the
|
||||
;; cron daemon up, and remove the temporary file. If the parse fails,
|
||||
;; we give user a choice of editing the file again or quitting the
|
||||
;; program and losing all changes made.
|
||||
(--edit
|
||||
(let ((temp-file (string-append config-tmp-dir
|
||||
"/crontab."
|
||||
(number->string (getpid)))))
|
||||
(catch #t
|
||||
(λ () (copy-file crontab-file temp-file))
|
||||
(λ (key . args) (with-output-to-file temp-file noop)))
|
||||
(chown temp-file (getuid) (getgid))
|
||||
(let retry ()
|
||||
(system (string-append
|
||||
(or (getenv "VISUAL") (getenv "EDITOR") "vi")
|
||||
" "
|
||||
temp-file))
|
||||
(catch 'mcron-error
|
||||
(λ () (read-vixie-file temp-file))
|
||||
(λ (key exit-code . msg)
|
||||
(apply mcron-error 0 msg)
|
||||
(if (get-yes-no "Edit again?")
|
||||
(retry)
|
||||
(begin
|
||||
(mcron-error 0 "Crontab not changed")
|
||||
(primitive-exit 0))))))
|
||||
(copy-file temp-file crontab-file)
|
||||
(delete-file temp-file)
|
||||
(hit-server crontab-user)))
|
||||
|
||||
;; In the remove personality we simply make an effort to delete the
|
||||
;; crontab and wake the daemon. No worries if this fails.
|
||||
(--remove (catch #t (λ () (delete-file crontab-file)
|
||||
(hit-server crontab-user))
|
||||
noop))
|
||||
|
||||
;; XXX: This comment is wrong.
|
||||
;; In the case of the replace personality we loop over all the
|
||||
;; arguments on the command line, and for each one parse the file to
|
||||
;; make sure it is parseable (but subsequently ignore the
|
||||
;; configuration), and all being well we copy it to the crontab
|
||||
;; location; we deal with the standard input in the same way but
|
||||
;; different. :-) In either case the server is woken so that it will
|
||||
;; read the newly installed crontab.
|
||||
((not (null? files))
|
||||
(let ((input-file (car files)))
|
||||
(catch-mcron-error
|
||||
(if (string=? input-file "-")
|
||||
(let ((input-string (read-string)))
|
||||
(read-vixie-port (open-input-string input-string))
|
||||
(with-output-to-file crontab-file
|
||||
(λ () (display input-string))))
|
||||
(λ () (system (string-append
|
||||
(or (getenv "VISUAL") (getenv "EDITOR") "vi")
|
||||
" "
|
||||
temp-file)))
|
||||
(λ _ (exit/cleanup 1)))
|
||||
(case (status:exit-val (cdr (try-replace temp-file)))
|
||||
((9 10 11)
|
||||
(if (get-yes-no "Edit again?")
|
||||
(retry)
|
||||
(begin
|
||||
(read-vixie-file input-file)
|
||||
(copy-file input-file crontab-file))))
|
||||
(hit-server crontab-user)))
|
||||
(mcron-error 0 "Crontab not changed")
|
||||
(exit/cleanup 0))))
|
||||
(else => exit/cleanup)))))
|
||||
|
||||
;; The user is being silly. The message here is identical to the one
|
||||
;; Vixie cron used to put out, for total compatibility.
|
||||
(else (mcron-error 15
|
||||
"usage error: file name must be specified for replace."))))))
|
||||
;; Replace crontab with given file or stdin. If it is a file, it must
|
||||
;; be opened here and not in the setuid helper, to prevent accessing
|
||||
;; unauthorized files.
|
||||
((not (null? files))
|
||||
(let ((input-file (car files)))
|
||||
(unless (string=? input-file "-")
|
||||
(dup2 (fileno (open-file input-file "r")) 0))
|
||||
(exec-crontab-access "-R")))
|
||||
|
||||
;; The user is being silly. The message here is identical to the one
|
||||
;; Vixie cron used to put out, for total compatibility.
|
||||
(else (mcron-error 15
|
||||
"usage error: file name must be specified for replace.")))))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue