123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- ;;; GNU Guix --- Functional package management for GNU
- ;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
- ;;;
- ;;; 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 <http://www.gnu.org/licenses/>.
- (define-module (gnu system pam)
- #:use-module (guix records)
- #:use-module (guix derivations)
- #:use-module (guix gexp)
- #:use-module (gnu services)
- #:use-module (ice-9 match)
- #:use-module (srfi srfi-1)
- #:use-module (srfi srfi-9)
- #:use-module (srfi srfi-11)
- #:use-module (srfi srfi-26)
- #:use-module ((guix utils) #:select (%current-system))
- #:export (pam-service
- pam-service-name
- pam-service-account
- pam-service-auth
- pam-service-password
- pam-service-session
- pam-entry
- pam-entry-control
- pam-entry-module
- pam-entry-arguments
- pam-limits-entry
- pam-limits-entry-domain
- pam-limits-entry-type
- pam-limits-entry-item
- pam-limits-entry-value
- pam-limits-entry->string
- pam-services->directory
- unix-pam-service
- base-pam-services
- session-environment-service
- session-environment-service-type
- pam-root-service-type
- pam-root-service))
- ;;; Commentary:
- ;;;
- ;;; Configuration of the pluggable authentication modules (PAM).
- ;;;
- ;;; Code:
- ;; PAM services (see
- ;; <http://www.linux-pam.org/Linux-PAM-html/sag-configuration-file.html>.)
- (define-record-type* <pam-service> pam-service
- make-pam-service
- pam-service?
- (name pam-service-name) ; string
- ;; The four "management groups".
- (account pam-service-account ; list of <pam-entry>
- (default '()))
- (auth pam-service-auth
- (default '()))
- (password pam-service-password
- (default '()))
- (session pam-service-session
- (default '())))
- (define-record-type* <pam-entry> pam-entry
- make-pam-entry
- pam-entry?
- (control pam-entry-control) ; string
- (module pam-entry-module) ; file name
- (arguments pam-entry-arguments ; list of string-valued g-expressions
- (default '())))
- ;; PAM limits entries are used by the pam_limits PAM module to set or override
- ;; limits on system resources for user sessions. The format is specified
- ;; here: http://linux-pam.org/Linux-PAM-html/sag-pam_limits.html
- (define-record-type <pam-limits-entry>
- (make-pam-limits-entry domain type item value)
- pam-limits-entry?
- (domain pam-limits-entry-domain) ; string
- (type pam-limits-entry-type) ; symbol
- (item pam-limits-entry-item) ; symbol
- (value pam-limits-entry-value)) ; symbol or number
- (define (pam-limits-entry domain type item value)
- "Construct a pam-limits-entry ensuring that the provided values are valid."
- (define (valid? value)
- (case item
- ((priority) (number? value))
- ((nice) (and (number? value)
- (>= value -20)
- (<= value 19)))
- (else (or (and (number? value)
- (>= value -1))
- (member value '(unlimited infinity))))))
- (define items
- (list 'core 'data 'fsize
- 'memlock 'nofile 'rss
- 'stack 'cpu 'nproc
- 'as 'maxlogins 'maxsyslogins
- 'priority 'locks 'sigpending
- 'msgqueue 'nice 'rtprio))
- (when (not (member type '(hard soft both)))
- (error "invalid limit type" type))
- (when (not (member item items))
- (error "invalid limit item" item))
- (when (not (valid? value))
- (error "invalid limit value" value))
- (make-pam-limits-entry domain type item value))
- (define (pam-limits-entry->string entry)
- "Convert a pam-limits-entry record to a string."
- (match entry
- (($ <pam-limits-entry> domain type item value)
- (string-join (list domain
- (if (eq? type 'both)
- "-"
- (symbol->string type))
- (symbol->string item)
- (cond
- ((symbol? value)
- (symbol->string value))
- (else
- (number->string value))))
- " "))))
- (define (pam-service->configuration service)
- "Return the derivation building the configuration file for SERVICE, to be
- dumped in /etc/pam.d/NAME, where NAME is the name of SERVICE."
- (define (entry->gexp type entry)
- (match entry
- (($ <pam-entry> control module (arguments ...))
- #~(format #t "~a ~a ~a ~a~%"
- #$type #$control #$module
- (string-join (list #$@arguments))))))
- (match service
- (($ <pam-service> name account auth password session)
- (define builder
- #~(begin
- (with-output-to-file #$output
- (lambda ()
- #$@(append (map (cut entry->gexp "account" <>) account)
- (map (cut entry->gexp "auth" <>) auth)
- (map (cut entry->gexp "password" <>) password)
- (map (cut entry->gexp "session" <>) session))
- #t))))
- (computed-file name builder))))
- (define (pam-services->directory services)
- "Return the derivation to build the configuration directory to be used as
- /etc/pam.d for SERVICES."
- (let ((names (map pam-service-name services))
- (files (map pam-service->configuration services)))
- (define builder
- #~(begin
- (use-modules (ice-9 match)
- (srfi srfi-1))
- (mkdir #$output)
- (for-each (match-lambda
- ((name file)
- (symlink file (string-append #$output "/" name))))
- ;; Since <pam-service> objects cannot be compared with
- ;; 'equal?' since they contain gexps, which contain
- ;; closures, use 'delete-duplicates' on the build-side
- ;; instead. See <http://bugs.gnu.org/20037>.
- (delete-duplicates '#$(zip names files)))))
- (computed-file "pam.d" builder)))
- (define %pam-other-services
- ;; The "other" PAM configuration, which denies everything (see
- ;; <http://www.linux-pam.org/Linux-PAM-html/sag-configuration-example.html>.)
- (let ((deny (pam-entry
- (control "required")
- (module "pam_deny.so"))))
- (pam-service
- (name "other")
- (account (list deny))
- (auth (list deny))
- (password (list deny))
- (session (list deny)))))
- (define unix-pam-service
- (let ((unix (pam-entry
- (control "required")
- (module "pam_unix.so")))
- (env (pam-entry ; to honor /etc/environment.
- (control "required")
- (module "pam_env.so"))))
- (lambda* (name #:key allow-empty-passwords? (allow-root? #f) motd
- login-uid?)
- "Return a standard Unix-style PAM service for NAME. When
- ALLOW-EMPTY-PASSWORDS? is true, allow empty passwords. When ALLOW-ROOT? is
- true, allow root to run the command without authentication. When MOTD is
- true, it should be a file-like object used as the message-of-the-day.
- When LOGIN-UID? is true, require the 'pam_loginuid' module; that module sets
- /proc/self/loginuid, which the libc 'getlogin' function relies on."
- ;; See <http://www.linux-pam.org/Linux-PAM-html/sag-configuration-example.html>.
- (pam-service
- (name name)
- (account (list unix))
- (auth (append (if allow-root?
- (list (pam-entry
- (control "sufficient")
- (module "pam_rootok.so")))
- '())
- (list (if allow-empty-passwords?
- (pam-entry
- (control "required")
- (module "pam_unix.so")
- (arguments '("nullok")))
- unix))))
- (password (list (pam-entry
- (control "required")
- (module "pam_unix.so")
- ;; Store SHA-512 encrypted passwords in /etc/shadow.
- (arguments '("sha512" "shadow")))))
- (session `(,@(if motd
- (list (pam-entry
- (control "optional")
- (module "pam_motd.so")
- (arguments
- (list #~(string-append "motd=" #$motd)))))
- '())
- ,@(if login-uid?
- (list (pam-entry ;to fill in /proc/self/loginuid
- (control "required")
- (module "pam_loginuid.so")))
- '())
- ,env ,unix))))))
- (define (rootok-pam-service command)
- "Return a PAM service for COMMAND such that 'root' does not need to
- authenticate to run COMMAND."
- (let ((unix (pam-entry
- (control "required")
- (module "pam_unix.so"))))
- (pam-service
- (name command)
- (account (list unix))
- (auth (list (pam-entry
- (control "sufficient")
- (module "pam_rootok.so"))))
- (password (list unix))
- (session (list unix)))))
- (define* (base-pam-services #:key allow-empty-passwords?)
- "Return the list of basic PAM services everyone would want."
- ;; TODO: Add other Shadow programs?
- (append (list %pam-other-services)
- ;; These programs are setuid-root.
- (map (cut unix-pam-service <>
- #:allow-empty-passwords? allow-empty-passwords?)
- '("passwd" "sudo"))
- ;; This is setuid-root, as well. Allow root to run "su" without
- ;; authenticating.
- (list (unix-pam-service "su"
- #:allow-empty-passwords? allow-empty-passwords?
- #:allow-root? #t))
- ;; These programs are not setuid-root, and we want root to be able
- ;; to run them without having to authenticate (notably because
- ;; 'useradd' and 'groupadd' are run during system activation.)
- (map rootok-pam-service
- '("useradd" "userdel" "usermod"
- "groupadd" "groupdel" "groupmod"))))
- ;;;
- ;;; System-wide environment variables.
- ;;;
- (define (environment-variables->environment-file vars)
- "Return a file for pam_env(8) that contains environment variables VARS."
- (apply mixed-text-file "environment"
- (append-map (match-lambda
- ((key . value)
- (list key "=" value "\n")))
- vars)))
- (define session-environment-service-type
- (service-type
- (name 'session-environment)
- (extensions
- (list (service-extension
- etc-service-type
- (lambda (vars)
- (list `("environment"
- ,(environment-variables->environment-file vars)))))))
- (compose concatenate)
- (extend append)
- (description
- "Populate @file{/etc/environment}, which is honored by @code{pam_env},
- with the specified environment variables. The value of this service is a list
- of name/value pairs for environments variables, such as:
- @example
- '((\"TZ\" . \"Canada/Pacific\"))
- @end example\n")))
- (define (session-environment-service vars)
- "Return a service that builds the @file{/etc/environment}, which can be read
- by PAM-aware applications to set environment variables for sessions.
- VARS should be an association list in which both the keys and the values are
- strings or string-valued gexps."
- (service session-environment-service-type vars))
- ;;;
- ;;; PAM root service.
- ;;;
- ;; Overall PAM configuration: a list of services, plus a procedure that takes
- ;; one <pam-service> and returns a <pam-service>. The procedure is used to
- ;; implement cross-cutting concerns such as the use of the 'elogind.so'
- ;; session module that keeps track of logged-in users.
- (define-record-type* <pam-configuration>
- pam-configuration make-pam-configuration? pam-configuration?
- (services pam-configuration-services) ;list of <pam-service>
- (transform pam-configuration-transform)) ;procedure
- (define (/etc-entry config)
- "Return the /etc/pam.d entry corresponding to CONFIG."
- (match config
- (($ <pam-configuration> services transform)
- (let ((services (map transform services)))
- `(("pam.d" ,(pam-services->directory services)))))))
- (define (extend-configuration initial extensions)
- "Extend INITIAL with NEW."
- (let-values (((services procs)
- (partition pam-service? extensions)))
- (pam-configuration
- (services (append (pam-configuration-services initial)
- services))
- (transform (apply compose
- (pam-configuration-transform initial)
- procs)))))
- (define pam-root-service-type
- (service-type (name 'pam)
- (extensions (list (service-extension etc-service-type
- /etc-entry)))
- ;; Arguments include <pam-service> as well as procedures.
- (compose concatenate)
- (extend extend-configuration)
- (description
- "Configure the Pluggable Authentication Modules (PAM) for all
- the specified @dfn{PAM services}. Each PAM service corresponds to a program,
- such as @command{login} or @command{sshd}, and specifies for instance how the
- program may authenticate users or what it should do when opening a new
- session.")))
- (define* (pam-root-service base #:key (transform identity))
- "The \"root\" PAM service, which collects <pam-service> instance and turns
- them into a /etc/pam.d directory, including the <pam-service> listed in BASE.
- TRANSFORM is a procedure that takes a <pam-service> and returns a
- <pam-service>. It can be used to implement cross-cutting concerns that affect
- all the PAM services."
- (service pam-root-service-type
- (pam-configuration (services base)
- (transform transform))))
|