blob: 57566f45021e9043a9fae9cedb08e2dac8ac49a9 (
plain) (
tree)
|
|
;;;; 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)
#: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))
;; 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.
(define (get-yes-no prompt . re-prompt)
(unless (null? re-prompt)
(display "Please answer y or n.\n"))
(display (string-append prompt " "))
(let ((r (read-line)))
(if (not (string-null? r))
(case (string-ref r 0)
((#\y #\Y) #t)
((#\n #\N) #f)
(else (get-yes-no prompt #t)))
(get-yes-no prompt #t))))
;;;
;;; Entry point.
;;;
(define (main --user --edit --list --remove files)
(when config-debug (debug-enable 'backtrace))
;; 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
((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
(λ () (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
(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.")))))
|