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))))) | ||||
| 
 | ||||
|     ;; 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. | ||||
|   ;; 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.")) | ||||
| 
 | ||||
|       (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))) | ||||
|   ;; 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 | ||||
|          ;; 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 | ||||
|           (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"))))) | ||||
|      (--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, 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. | ||||
|      ;; 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 ((temp-file (string-append config-tmp-dir | ||||
|       (let* ((template (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)) | ||||
|                                       (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 () | ||||
|               (system (string-append | ||||
|           (catch #t | ||||
|             (λ () (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) | ||||
|                            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") | ||||
|                         (primitive-exit 0)))))) | ||||
|             (copy-file temp-file crontab-file) | ||||
|             (delete-file temp-file) | ||||
|             (hit-server crontab-user))) | ||||
|                    (exit/cleanup 0)))) | ||||
|             (else => exit/cleanup))))) | ||||
| 
 | ||||
|          ;; 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. | ||||
|      ;; 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))) | ||||
|             (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)))) | ||||
|                  (begin | ||||
|                    (read-vixie-file input-file) | ||||
|                    (copy-file input-file crontab-file)))) | ||||
|             (hit-server crontab-user))) | ||||
|         (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.")))))) | ||||
|              "usage error: file name must be specified for replace."))))) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dale Mellor
				Dale Mellor