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 | if MULTI_USER | ||||||
| bin_SCRIPTS += bin/crontab | bin_SCRIPTS += bin/crontab | ||||||
| sbin_SCRIPTS = bin/cron | sbin_SCRIPTS = bin/cron | ||||||
|  | libexec_SCRIPTS = bin/crontab-access-real | ||||||
|  | sbin_PROGRAMS = bin/crontab-access | ||||||
| else | else | ||||||
| noinst_SCRIPTS += bin/cron bin/crontab | noinst_SCRIPTS += bin/cron bin/crontab bin/crontab-access-real | ||||||
|  | noinst_PROGRAMS = bin/crontab-access | ||||||
| endif | 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. | # wrapper to be used in the build environment and for running tests. | ||||||
| noinst_SCRIPTS += pre-inst-env | noinst_SCRIPTS += pre-inst-env | ||||||
| 
 | 
 | ||||||
|  | @ -68,6 +75,7 @@ pkgscriptdir = $(pkgmoduledir)/scripts | ||||||
| dist_pkgscript_DATA = \ | dist_pkgscript_DATA = \ | ||||||
|   src/mcron/scripts/cron.scm \ |   src/mcron/scripts/cron.scm \ | ||||||
|   src/mcron/scripts/crontab.scm \ |   src/mcron/scripts/crontab.scm \ | ||||||
|  |   src/mcron/scripts/crontab-access.scm \ | ||||||
|   src/mcron/scripts/mcron.scm |   src/mcron/scripts/mcron.scm | ||||||
| 
 | 
 | ||||||
| pkgscriptgodir = $(pkgmodulegodir)/scripts | pkgscriptgodir = $(pkgmodulegodir)/scripts | ||||||
|  | @ -77,7 +85,13 @@ compiled_modules = \ | ||||||
|   $(pkgmodulego_DATA) \ |   $(pkgmodulego_DATA) \ | ||||||
|   $(pkgscriptgo_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 | DISTCLEANFILES = src/mcron/config.scm | ||||||
| 
 | 
 | ||||||
| # Unset 'GUILE_LOAD_COMPILED_PATH' altogether while compiling.  Otherwise, if | # 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 \ | 	  --warn=format --warn=unbound-variable --warn=arity-mismatch \ | ||||||
| 	  --target="$(host)" --output="$@" "$<" $(devnull_verbose) | 	  --target="$(host)" --output="$@" "$<" $(devnull_verbose) | ||||||
| 
 | 
 | ||||||
| 
 | do_subst = sed	-e 's,%PREFIX%,${prefix},g'				\ | ||||||
| bin/% : src/%.in Makefile | 		-e 's,%sbindir%,${sbindir},g'				\ | ||||||
| 	$(AM_V_GEN)$(MKDIR_P) bin ; \ | 		-e 's,%libexecdir%,${libexecdir},g'			\ | ||||||
| 	  sed	-e 's,%PREFIX%,${prefix},g'				\ |  | ||||||
| 		-e 's,%modsrcdir%,${guilesitedir},g'			\ | 		-e 's,%modsrcdir%,${guilesitedir},g'			\ | ||||||
| 		-e 's,%modbuilddir%,${guilesitegodir},g'		\ | 		-e 's,%modbuilddir%,${guilesitegodir},g'		\ | ||||||
| 		-e 's,%localstatedir%,${localstatedir},g'		\ | 		-e 's,%localstatedir%,${localstatedir},g'		\ | ||||||
|  | @ -114,8 +127,17 @@ bin/% : src/%.in Makefile | ||||||
| 		-e 's,%PACKAGE_BUGREPORT%,@PACKAGE_BUGREPORT@,g'	\ | 		-e 's,%PACKAGE_BUGREPORT%,@PACKAGE_BUGREPORT@,g'	\ | ||||||
| 		-e 's,%PACKAGE_NAME%,@PACKAGE_NAME@,g'			\ | 		-e 's,%PACKAGE_NAME%,@PACKAGE_NAME@,g'			\ | ||||||
| 		-e 's,%PACKAGE_URL%,@PACKAGE_URL@,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 $@ | 	  chmod a+x $@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -153,6 +175,8 @@ EXTRA_DIST = \ | ||||||
|   HACKING \ |   HACKING \ | ||||||
|   src/cron.in \ |   src/cron.in \ | ||||||
|   src/crontab.in \ |   src/crontab.in \ | ||||||
|  |   src/crontab-access-real.in \ | ||||||
|  |   src/crontab-access.c.in \ | ||||||
|   src/mcron.in \ |   src/mcron.in \ | ||||||
|   tests/init.sh \ |   tests/init.sh \ | ||||||
|   $(TESTS) |   $(TESTS) | ||||||
|  | @ -166,10 +190,10 @@ transform_exe = s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/ | ||||||
| 
 | 
 | ||||||
| install-exec-hook: | install-exec-hook: | ||||||
| if MULTI_USER | if MULTI_USER | ||||||
| 	tcrontab=`echo crontab | sed '$(transform_exe)'`; \ | 	tcrontab=`echo crontab | sed '$(transform_exe)'`; | ||||||
| 	chmod u+s $(DESTDIR)$(bindir)/$${tcrontab} | 	tcrontab_access=`echo crontab-access | sed '$(transform_exe)'`; \ | ||||||
| 	tcron=`echo cron | sed '$(transform_exe)'`; \ | 	chmod u+s $(DESTDIR)$(sbindir)/$${tcrontab_access} | ||||||
| 	chmod u+s $(DESTDIR)$(sbindir)/$${tcron} | 	tcron=`echo cron | sed '$(transform_exe)'`; | ||||||
| endif | endif | ||||||
| 	tmcron=`echo mcron | sed '$(transform_exe)'`; | 	tmcron=`echo mcron | sed '$(transform_exe)'`; | ||||||
| 
 | 
 | ||||||
|  | @ -178,8 +202,9 @@ installcheck-local: | ||||||
| 	tmcron=`echo mcron | sed '$(transform_exe)'`; \ | 	tmcron=`echo mcron | sed '$(transform_exe)'`; \ | ||||||
| 	test -e $(DESTDIR)$(bindir)/$${tmcron} | 	test -e $(DESTDIR)$(bindir)/$${tmcron} | ||||||
| if MULTI_USER | if MULTI_USER | ||||||
| 	tcrontab=`echo crontab | sed '$(transform_exe)'`; \ | 	tcrontab=`echo crontab | sed '$(transform_exe)'`; | ||||||
| 	test -u $(DESTDIR)$(bindir)/$${tcrontab} | 	tcrontab_access=`echo crontab | sed '$(transform_exe)'`; \ | ||||||
|  | 	test -u $(DESTDIR)$(bindir)/$${tcrontab_access} | ||||||
| 	tcron=`echo cron | sed '$(transform_exe)'`; \ | 	tcron=`echo cron | sed '$(transform_exe)'`; \ | ||||||
| 	test -e $(DESTDIR)$(sbindir)/$${tcron} | 	test -e $(DESTDIR)$(sbindir)/$${tcron} | ||||||
| else !MULTI_USER | 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])], |     [Don't Install legacy cron and crontab programs])], | ||||||
|   [enable_multi_user="$enableval"], |   [enable_multi_user="$enableval"], | ||||||
|   [enable_multi_user="yes"]) |   [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]) | AM_CONDITIONAL([MULTI_USER], [test "x$enable_multi_user" = xyes]) | ||||||
| 
 | 
 | ||||||
| # Configure the various files that mcron uses at runtime. | # 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]) |                 [chmod +x pre-inst-env]) | ||||||
| AC_CONFIG_FILES([doc/config.texi | AC_CONFIG_FILES([doc/config.texi | ||||||
|                  Makefile |                  Makefile | ||||||
|                  src/mcron/config.scm]) |                  src/mcron/config.scm.in]) | ||||||
| AC_OUTPUT | 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-package-url "@PACKAGE_URL@") | ||||||
| (define-public config-sendmail "@SENDMAIL@") | (define-public config-sendmail "@SENDMAIL@") | ||||||
| 
 | 
 | ||||||
|  | (define-public config-sbin-dir "%sbindir%") | ||||||
| (define-public config-spool-dir "@CONFIG_SPOOL_DIR@") | (define-public config-spool-dir "@CONFIG_SPOOL_DIR@") | ||||||
| (define-public config-socket-file "@CONFIG_SOCKET_FILE@") | (define-public config-socket-file "@CONFIG_SOCKET_FILE@") | ||||||
| (define-public config-allow-file "@CONFIG_ALLOW_FILE@") | (define-public config-allow-file "@CONFIG_ALLOW_FILE@") | ||||||
|  | @ -147,7 +147,7 @@ option.\n") | ||||||
|                                              (delete-file config-socket-file)) |                                              (delete-file config-socket-file)) | ||||||
|                                            noop) |                                            noop) | ||||||
|                              (exit EXIT_FAILURE)))) |                              (exit EXIT_FAILURE)))) | ||||||
|                 '(SIGTERM SIGINT SIGQUIT SIGHUP)) |               (list SIGTERM SIGINT SIGQUIT SIGHUP)) | ||||||
| 
 | 
 | ||||||
|   ;; We can now write the PID file. |   ;; We can now write the PID file. | ||||||
|   (with-output-to-file  config-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) | (define-module (mcron scripts crontab) | ||||||
|   #:use-module (ice-9 rdelim) |   #:use-module (ice-9 rdelim) | ||||||
|  |   #:use-module (srfi srfi-1) | ||||||
|   #:use-module (mcron config) |   #:use-module (mcron config) | ||||||
|   #:use-module (mcron utils) |   #:use-module (mcron utils) | ||||||
|   #:use-module (mcron vixie-specification) |   #:use-module (mcron vixie-specification) | ||||||
|   #:export (main)) |   #: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 | ;; 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', | ;; answer begins with 'y' or 'Y', return #f if it begins with 'n' or 'N', | ||||||
| ;; otherwise ask again. | ;; 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. | ;;; 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) | (define (main --user --edit --list --remove files) | ||||||
|   (when config-debug  (debug-enable 'backtrace)) |   (when config-debug  (debug-enable 'backtrace)) | ||||||
|   (let ((crontab-real-user |   ;; Check that no more than one of the mutually exclusive options are | ||||||
|          ;; This program should have been installed SUID root. Here we get |   ;; being used. | ||||||
|          ;; the passwd entry for the real user who is running this program. |   (when (<  1  (+ (if --edit 1 0) (if --list 1 0) (if --remove 1 0))) | ||||||
|          (passwd:name (getpw (getuid))))) |     (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 |   ;; Check that a non-root user is trying to read someone else's files. | ||||||
|     ;; /var/cron/allow and/or /var/cron/deny files, bomb out now. |   ;; This will be enforced in the setuid helper 'crontab-access', but good | ||||||
|     (if (or (eq? (in-access-file? config-allow-file crontab-real-user) #f) |   ;; to let the user know early. | ||||||
|             (eq? (in-access-file? config-deny-file crontab-real-user) #t)) |   (when (and (not (zero? (getuid))) --user) | ||||||
|         (mcron-error 6 "Access denied by system operator.")) |     (mcron-error 8 "Only root can use the -u option.")) | ||||||
| 
 | 
 | ||||||
|     ;; Check that no more than one of the mutually exclusive options are |   ;; Crontabs being edited should not be global or group-readable. | ||||||
|     ;; being used. |   (umask #o077) | ||||||
|       (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. |   (let ((user-args (if --user (list "-u" --user) '()))) | ||||||
|       (when (and (not (zero? (getuid))) --user) |     (define (usable-crontab-access? filename) | ||||||
|         (mcron-error 8 "Only root can use the -u option.")) |       (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 |     (define crontab-access | ||||||
|                 ;; different from the real user. |       (find usable-crontab-access? | ||||||
|                 (crontab-user (or --user crontab-real-user)) |             (map (λ (f) (string-append f "/crontab-access")) | ||||||
|                 ;; So now we know which crontab file we will be manipulating. |                  (cons config-sbin-dir | ||||||
|                 (crontab-file |                        (or (and=> (getenv "PATH") parse-path) | ||||||
|                          (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 |     (define (exec-crontab-access . args) | ||||||
|         ;; options but supplies file names on the command line). |       (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 |         (cond | ||||||
|          ;; In the list personality, we simply open the crontab and copy it |          ((zero? pid) | ||||||
|          ;; character-by-character to the standard output. If anything goes |           (for-each dup2 srcs dsts) | ||||||
|          ;; wrong, it can only mean that this user does not have a crontab |           (for-each close-fdes closes) | ||||||
|          ;; file. |           (apply exec-crontab-access args)) | ||||||
|          (--list |          (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 |           (catch #t | ||||||
|             (λ () |             (λ () (system (string-append | ||||||
|               (with-input-from-file crontab-file |                            (or (getenv "VISUAL") (getenv "EDITOR") "vi") | ||||||
|                 (λ () |                            " " | ||||||
|                   (do ((input (read-char) (read-char))) |                            temp-file))) | ||||||
|                       ((eof-object? input)) |             (λ _ (exit/cleanup 1))) | ||||||
|                     (display input))))) |           (case (status:exit-val (cdr (try-replace temp-file))) | ||||||
|             (λ (key . args) |             ((9 10 11) | ||||||
|               (display (string-append "No crontab for " |              (if (get-yes-no "Edit again?") | ||||||
|                                       crontab-user |                  (retry) | ||||||
|                                       " 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)))) |  | ||||||
|                  (begin |                  (begin | ||||||
|                    (read-vixie-file input-file) |                    (mcron-error 0 "Crontab not changed") | ||||||
|                    (copy-file input-file crontab-file)))) |                    (exit/cleanup 0)))) | ||||||
|             (hit-server crontab-user))) |             (else => exit/cleanup))))) | ||||||
| 
 | 
 | ||||||
|          ;; The user is being silly. The message here is identical to the one |      ;; Replace crontab with given file or stdin.  If it is a file, it must | ||||||
|          ;; Vixie cron used to put out, for total compatibility. |      ;; be opened here and not in the setuid helper, to prevent accessing | ||||||
|          (else (mcron-error 15 |      ;; unauthorized files. | ||||||
|                  "usage error: file name must be specified for replace.")))))) |      ((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
	
	 Dale Mellor
				Dale Mellor