diff options
author | Dale Mellor <mcron-lsfnyl@rdmp.org> | 2023-03-18 14:18:17 +0000 |
---|---|---|
committer | Dale Mellor <mcron-lsfnyl@rdmp.org> | 2023-03-18 14:18:17 +0000 |
commit | 0fe4d2cc9544d24ecc3e74a2d92433e01b9e25c6 (patch) | |
tree | 2cf2ffe793211a8c48ff86a9db57b156f2cddd88 | |
parent | fd19e63490e30fb5ae4cc159f72b8f40952ceb9d (diff) | |
parent | 4727c770c2a723d46b4162cff21ab8f0b75c3998 (diff) | |
download | mcron-0fe4d2cc9544d24ecc3e74a2d92433e01b9e25c6.tar.gz mcron-0fe4d2cc9544d24ecc3e74a2d92433e01b9e25c6.tar.bz2 mcron-0fe4d2cc9544d24ecc3e74a2d92433e01b9e25c6.zip |
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.
-rwxr-xr-x | Makefile.am | 53 | ||||
-rw-r--r-- | configure.ac | 10 | ||||
-rw-r--r-- | src/crontab-access-real.in | 45 | ||||
-rw-r--r-- | src/crontab-access.c.in | 10 | ||||
-rw-r--r-- | src/mcron/config.scm.in.in (renamed from src/mcron/config.scm.in) | 1 | ||||
-rw-r--r-- | src/mcron/scripts/cron.scm | 2 | ||||
-rw-r--r-- | src/mcron/scripts/crontab-access.scm | 121 | ||||
-rw-r--r-- | src/mcron/scripts/crontab.scm | 255 |
8 files changed, 338 insertions, 159 deletions
diff --git a/Makefile.am b/Makefile.am index 09a0d6d..2a87d72 100755 --- a/Makefile.am +++ b/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 diff --git a/configure.ac b/configure.ac index a732f19..784a5a1 100644 --- a/configure.ac +++ b/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 diff --git a/src/crontab-access-real.in b/src/crontab-access-real.in new file mode 100644 index 0000000..569d147 --- /dev/null +++ b/src/crontab-access-real.in @@ -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) diff --git a/src/crontab-access.c.in b/src/crontab-access.c.in new file mode 100644 index 0000000..652b9cc --- /dev/null +++ b/src/crontab-access.c.in @@ -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; +} diff --git a/src/mcron/config.scm.in b/src/mcron/config.scm.in.in index e6a0e07..769a913 100644 --- a/src/mcron/config.scm.in +++ b/src/mcron/config.scm.in.in @@ -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@") diff --git a/src/mcron/scripts/cron.scm b/src/mcron/scripts/cron.scm index 2e36d87..3c3a57c 100644 --- a/src/mcron/scripts/cron.scm +++ b/src/mcron/scripts/cron.scm @@ -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 diff --git a/src/mcron/scripts/crontab-access.scm b/src/mcron/scripts/crontab-access.scm new file mode 100644 index 0000000..d97fc62 --- /dev/null +++ b/src/mcron/scripts/crontab-access.scm @@ -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")))))))) diff --git a/src/mcron/scripts/crontab.scm b/src/mcron/scripts/crontab.scm index 0c5c47f..57566f4 100644 --- a/src/mcron/scripts/crontab.scm +++ b/src/mcron/scripts/crontab.scm @@ -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))))) - - ;; 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 --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.")) - - ;; 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.")) - - (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). + ;; 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.")) + + ;; 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.")) + + ;; Crontabs being edited should not be global or group-readable. + (umask #o077) + + (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)))))) + + (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))) - - ;; 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.")))))) + (mcron-error 0 "Crontab not changed") + (exit/cleanup 0)))) + (else => exit/cleanup))))) + + ;; 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."))))) |