;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès ;;; Copyright © 2013 Nikita Karetnikov ;;; Copyright © 2013 Andreas Enge ;;; Copyright © 2015, 2018 Mark H Weaver ;;; Copyright © 2018 Arun Isaac ;;; Copyright © 2018, 2019 Ricardo Wurmus ;;; Copyright © 2021 Maxime Devos ;;; Copyright © 2020 Christine Lemmer-Webber ;;; Copyright © 2021 Brice Waegeneire ;;; ;;; This file is part of GNU Guix. ;;; ;;; GNU Guix 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 Guix 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 Guix. If not, see . (define-module (gnu build activation) #:use-module (gnu system accounts) #:use-module (gnu system setuid) #:use-module (gnu build accounts) #:use-module (gnu build linux-boot) #:use-module (guix build utils) #:use-module ((guix build syscalls) #:select (with-file-lock)) #:use-module (ice-9 ftw) #:use-module (ice-9 match) #:use-module (ice-9 vlist) #:use-module (srfi srfi-1) #:use-module (srfi srfi-11) #:use-module (srfi srfi-26) #:export (activate-users+groups activate-user-home activate-etc activate-setuid-programs activate-special-files activate-modprobe activate-firmware activate-ptrace-attach activate-current-system mkdir-p/perms)) ;;; Commentary: ;;; ;;; This module provides "activation" helpers. Activation is the process that ;;; consists in setting up system-wide files and directories so that an ;;; 'operating-system' configuration becomes active. ;;; ;;; Code: (define %skeleton-directory ;; Directory containing skeleton files for new accounts. ;; Note: keep the trailing '/' so that 'scandir' enters it. "/etc/skel/") (define (dot-or-dot-dot? file) (member file '("." ".."))) ;; Based upon mkdir-p from (guix build utils) (define (verify-not-symbolic dir) "Verify DIR or its ancestors aren't symbolic links." (define absolute? (string-prefix? "/" dir)) (define not-slash (char-set-complement (char-set #\/))) (define (verify-component file) (unless (eq? 'directory (stat:type (lstat file))) (error "file name component is not a directory" dir))) (let loop ((components (string-tokenize dir not-slash)) (root (if absolute? "" "."))) (match components ((head tail ...) (let ((file (string-append root "/" head))) (catch 'system-error (lambda () (verify-component file) (loop tail file)) (lambda args (if (= ENOENT (system-error-errno args)) #t (apply throw args)))))) (() #t)))) ;; TODO: the TOCTTOU race can be addressed once guile has bindings ;; for fstatat, openat and friends. (define (mkdir-p/perms directory owner bits) "Create the directory DIRECTORY and all its ancestors. Verify no component of DIRECTORY is a symbolic link. Warning: this is currently suspect to a TOCTTOU race!" (verify-not-symbolic directory) (mkdir-p directory) (chown directory (passwd:uid owner) (passwd:gid owner)) (chmod directory bits)) (define* (copy-account-skeletons home #:key (directory %skeleton-directory) uid gid) "Copy the account skeletons from DIRECTORY to HOME. When UID is an integer, make it the owner of all the files created except the home directory; likewise for GID." (define (set-owner file) (when (or uid gid) (chown file (or uid -1) (or gid -1)))) (let ((files (scandir directory (negate dot-or-dot-dot?) string /run/current-system/profile/etc/ssl symlink. This ;; symlink, to a target outside of the store, probably doesn't belong in the ;; static 'etc' store directory. However, if it were to be put there, ;; beware that if /run/current-system/profile/etc/ssl doesn't exist at the ;; time of activation (e.g. when installing a fresh system), the call to ;; 'file-is-directory?' below will fail because it uses 'stat', not 'lstat'. (rm-f "/etc/ssl") (symlink "/run/current-system/profile/etc/ssl" "/etc/ssl") (rm-f "/etc/static") (symlink etc "/etc/static") (for-each (lambda (file) (let ((target (string-append "/etc/" file)) (source (string-append "/etc/static/" file))) (rm-f target) ;; Things such as /etc/sudoers must be regular files, not ;; symlinks; furthermore, they could be modified behind our ;; back---e.g., with 'visudo'. Thus, make a copy instead of ;; symlinking them. (if (file-is-directory? source) (symlink source target) (copy-file source target)) ;; XXX: Dirty hack to meet sudo's expectations. (when (string=? (basename target) "sudoers") (chmod target #o440)))) (scandir etc (negate dot-or-dot-dot?) ;; The default is 'string-locale)) (scandir %setuid-directory (lambda (file) (not (member file '("." "..")))) string. (format (current-error-port) "warning: failed to make ~s setuid/setgid: ~a~%" (setuid-program-program program) (strerror (system-error-errno args)))))) programs)) (define (activate-special-files special-files) "Install the files listed in SPECIAL-FILES. Each element of SPECIAL-FILES is a pair where the first element is the name of the special file and the second element is the name it should appear at, such as: ((\"/bin/sh\" \"/gnu/store/…-bash/bin/sh\") (\"/usr/bin/env\" \"/gnu/store/…-coreutils/bin/env\")) " (define install-special-file (match-lambda ((or (target file) (? pair? (= car target) (= cdr file))) (let ((pivot (string-append target ".new"))) (mkdir-p (dirname target)) (symlink file pivot) (rename-file pivot target))))) (for-each install-special-file special-files)) (define (activate-modprobe modprobe) "Tell the kernel to use MODPROBE to load modules." ;; If the kernel was built without loadable module support, this file is ;; unavailable, so check for its existence first. (when (file-exists? "/proc/sys/kernel/modprobe") (call-with-output-file "/proc/sys/kernel/modprobe" (lambda (port) (display modprobe port))))) (define (activate-firmware directory) "Tell the kernel to look for device firmware under DIRECTORY. This mechanism bypasses udev: it allows Linux to handle firmware loading directly by itself, without having to resort to a \"user helper\"." (call-with-output-file "/sys/module/firmware_class/parameters/path" (lambda (port) (display directory port)))) (define (activate-ptrace-attach) "Allow users to PTRACE_ATTACH their own processes. This works around a regression introduced in the default \"security\" policy found in Linux 3.4 onward that prevents users from attaching to their own processes--see Yama.txt in the Linux source tree for the rationale. This sounds like an unacceptable restriction for little or no security improvement." (let ((file "/proc/sys/kernel/yama/ptrace_scope")) (when (file-exists? file) (call-with-output-file file (lambda (port) (display 0 port)))))) (define %current-system ;; The system that is current (a symlink.) This is not necessarily the same ;; as the system we booted (aka. /run/booted-system) because we can re-build ;; a new system configuration and activate it, without rebooting. "/run/current-system") (define (boot-time-system) "Return the 'gnu.system' argument passed on the kernel command line." (find-long-option "gnu.system" (if (string-contains %host-type "linux-gnu") (linux-command-line) (command-line)))) (define* (activate-current-system #:optional (system (or (getenv "GUIX_NEW_SYSTEM") (boot-time-system)))) "Atomically make SYSTEM the current system." ;; The 'GUIX_NEW_SYSTEM' environment variable is used as a way for 'guix ;; system reconfigure' to pass the file name of the new system. (format #t "making '~a' the current system...~%" system) ;; Atomically make SYSTEM current. (let ((new (string-append %current-system ".new"))) (symlink system new) (rename-file new %current-system))) ;;; activation.scm ends here