pam.scm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (gnu system pam)
  19. #:use-module (guix records)
  20. #:use-module (guix derivations)
  21. #:use-module (guix gexp)
  22. #:use-module (gnu services)
  23. #:use-module (ice-9 match)
  24. #:use-module (srfi srfi-1)
  25. #:use-module (srfi srfi-9)
  26. #:use-module (srfi srfi-11)
  27. #:use-module (srfi srfi-26)
  28. #:use-module ((guix utils) #:select (%current-system))
  29. #:export (pam-service
  30. pam-service-name
  31. pam-service-account
  32. pam-service-auth
  33. pam-service-password
  34. pam-service-session
  35. pam-entry
  36. pam-entry-control
  37. pam-entry-module
  38. pam-entry-arguments
  39. pam-limits-entry
  40. pam-limits-entry-domain
  41. pam-limits-entry-type
  42. pam-limits-entry-item
  43. pam-limits-entry-value
  44. pam-limits-entry->string
  45. pam-services->directory
  46. unix-pam-service
  47. base-pam-services
  48. session-environment-service
  49. session-environment-service-type
  50. pam-root-service-type
  51. pam-root-service))
  52. ;;; Commentary:
  53. ;;;
  54. ;;; Configuration of the pluggable authentication modules (PAM).
  55. ;;;
  56. ;;; Code:
  57. ;; PAM services (see
  58. ;; <http://www.linux-pam.org/Linux-PAM-html/sag-configuration-file.html>.)
  59. (define-record-type* <pam-service> pam-service
  60. make-pam-service
  61. pam-service?
  62. (name pam-service-name) ; string
  63. ;; The four "management groups".
  64. (account pam-service-account ; list of <pam-entry>
  65. (default '()))
  66. (auth pam-service-auth
  67. (default '()))
  68. (password pam-service-password
  69. (default '()))
  70. (session pam-service-session
  71. (default '())))
  72. (define-record-type* <pam-entry> pam-entry
  73. make-pam-entry
  74. pam-entry?
  75. (control pam-entry-control) ; string
  76. (module pam-entry-module) ; file name
  77. (arguments pam-entry-arguments ; list of string-valued g-expressions
  78. (default '())))
  79. ;; PAM limits entries are used by the pam_limits PAM module to set or override
  80. ;; limits on system resources for user sessions. The format is specified
  81. ;; here: http://linux-pam.org/Linux-PAM-html/sag-pam_limits.html
  82. (define-record-type <pam-limits-entry>
  83. (make-pam-limits-entry domain type item value)
  84. pam-limits-entry?
  85. (domain pam-limits-entry-domain) ; string
  86. (type pam-limits-entry-type) ; symbol
  87. (item pam-limits-entry-item) ; symbol
  88. (value pam-limits-entry-value)) ; symbol or number
  89. (define (pam-limits-entry domain type item value)
  90. "Construct a pam-limits-entry ensuring that the provided values are valid."
  91. (define (valid? value)
  92. (case item
  93. ((priority) (number? value))
  94. ((nice) (and (number? value)
  95. (>= value -20)
  96. (<= value 19)))
  97. (else (or (and (number? value)
  98. (>= value -1))
  99. (member value '(unlimited infinity))))))
  100. (define items
  101. (list 'core 'data 'fsize
  102. 'memlock 'nofile 'rss
  103. 'stack 'cpu 'nproc
  104. 'as 'maxlogins 'maxsyslogins
  105. 'priority 'locks 'sigpending
  106. 'msgqueue 'nice 'rtprio))
  107. (when (not (member type '(hard soft both)))
  108. (error "invalid limit type" type))
  109. (when (not (member item items))
  110. (error "invalid limit item" item))
  111. (when (not (valid? value))
  112. (error "invalid limit value" value))
  113. (make-pam-limits-entry domain type item value))
  114. (define (pam-limits-entry->string entry)
  115. "Convert a pam-limits-entry record to a string."
  116. (match entry
  117. (($ <pam-limits-entry> domain type item value)
  118. (string-join (list domain
  119. (if (eq? type 'both)
  120. "-"
  121. (symbol->string type))
  122. (symbol->string item)
  123. (cond
  124. ((symbol? value)
  125. (symbol->string value))
  126. (else
  127. (number->string value))))
  128. " "))))
  129. (define (pam-service->configuration service)
  130. "Return the derivation building the configuration file for SERVICE, to be
  131. dumped in /etc/pam.d/NAME, where NAME is the name of SERVICE."
  132. (define (entry->gexp type entry)
  133. (match entry
  134. (($ <pam-entry> control module (arguments ...))
  135. #~(format #t "~a ~a ~a ~a~%"
  136. #$type #$control #$module
  137. (string-join (list #$@arguments))))))
  138. (match service
  139. (($ <pam-service> name account auth password session)
  140. (define builder
  141. #~(begin
  142. (with-output-to-file #$output
  143. (lambda ()
  144. #$@(append (map (cut entry->gexp "account" <>) account)
  145. (map (cut entry->gexp "auth" <>) auth)
  146. (map (cut entry->gexp "password" <>) password)
  147. (map (cut entry->gexp "session" <>) session))
  148. #t))))
  149. (computed-file name builder))))
  150. (define (pam-services->directory services)
  151. "Return the derivation to build the configuration directory to be used as
  152. /etc/pam.d for SERVICES."
  153. (let ((names (map pam-service-name services))
  154. (files (map pam-service->configuration services)))
  155. (define builder
  156. #~(begin
  157. (use-modules (ice-9 match)
  158. (srfi srfi-1))
  159. (mkdir #$output)
  160. (for-each (match-lambda
  161. ((name file)
  162. (symlink file (string-append #$output "/" name))))
  163. ;; Since <pam-service> objects cannot be compared with
  164. ;; 'equal?' since they contain gexps, which contain
  165. ;; closures, use 'delete-duplicates' on the build-side
  166. ;; instead. See <http://bugs.gnu.org/20037>.
  167. (delete-duplicates '#$(zip names files)))))
  168. (computed-file "pam.d" builder)))
  169. (define %pam-other-services
  170. ;; The "other" PAM configuration, which denies everything (see
  171. ;; <http://www.linux-pam.org/Linux-PAM-html/sag-configuration-example.html>.)
  172. (let ((deny (pam-entry
  173. (control "required")
  174. (module "pam_deny.so"))))
  175. (pam-service
  176. (name "other")
  177. (account (list deny))
  178. (auth (list deny))
  179. (password (list deny))
  180. (session (list deny)))))
  181. (define unix-pam-service
  182. (let ((unix (pam-entry
  183. (control "required")
  184. (module "pam_unix.so")))
  185. (env (pam-entry ; to honor /etc/environment.
  186. (control "required")
  187. (module "pam_env.so"))))
  188. (lambda* (name #:key allow-empty-passwords? (allow-root? #f) motd)
  189. "Return a standard Unix-style PAM service for NAME. When
  190. ALLOW-EMPTY-PASSWORDS? is true, allow empty passwords. When ALLOW-ROOT? is
  191. true, allow root to run the command without authentication. When MOTD is
  192. true, it should be a file-like object used as the message-of-the-day."
  193. ;; See <http://www.linux-pam.org/Linux-PAM-html/sag-configuration-example.html>.
  194. (let ((name* name))
  195. (pam-service
  196. (name name*)
  197. (account (list unix))
  198. (auth (append (if allow-root?
  199. (list (pam-entry
  200. (control "sufficient")
  201. (module "pam_rootok.so")))
  202. '())
  203. (list (if allow-empty-passwords?
  204. (pam-entry
  205. (control "required")
  206. (module "pam_unix.so")
  207. (arguments '("nullok")))
  208. unix))))
  209. (password (list (pam-entry
  210. (control "required")
  211. (module "pam_unix.so")
  212. ;; Store SHA-512 encrypted passwords in /etc/shadow.
  213. (arguments '("sha512" "shadow")))))
  214. (session (if motd
  215. (list env unix
  216. (pam-entry
  217. (control "optional")
  218. (module "pam_motd.so")
  219. (arguments
  220. (list #~(string-append "motd=" #$motd)))))
  221. (list env unix))))))))
  222. (define (rootok-pam-service command)
  223. "Return a PAM service for COMMAND such that 'root' does not need to
  224. authenticate to run COMMAND."
  225. (let ((unix (pam-entry
  226. (control "required")
  227. (module "pam_unix.so"))))
  228. (pam-service
  229. (name command)
  230. (account (list unix))
  231. (auth (list (pam-entry
  232. (control "sufficient")
  233. (module "pam_rootok.so"))))
  234. (password (list unix))
  235. (session (list unix)))))
  236. (define* (base-pam-services #:key allow-empty-passwords?)
  237. "Return the list of basic PAM services everyone would want."
  238. ;; TODO: Add other Shadow programs?
  239. (append (list %pam-other-services)
  240. ;; These programs are setuid-root.
  241. (map (cut unix-pam-service <>
  242. #:allow-empty-passwords? allow-empty-passwords?)
  243. '("passwd" "sudo"))
  244. ;; This is setuid-root, as well. Allow root to run "su" without
  245. ;; authenticating.
  246. (list (unix-pam-service "su"
  247. #:allow-empty-passwords? allow-empty-passwords?
  248. #:allow-root? #t))
  249. ;; These programs are not setuid-root, and we want root to be able
  250. ;; to run them without having to authenticate (notably because
  251. ;; 'useradd' and 'groupadd' are run during system activation.)
  252. (map rootok-pam-service
  253. '("useradd" "userdel" "usermod"
  254. "groupadd" "groupdel" "groupmod"))))
  255. ;;;
  256. ;;; System-wide environment variables.
  257. ;;;
  258. (define (environment-variables->environment-file vars)
  259. "Return a file for pam_env(8) that contains environment variables VARS."
  260. (apply mixed-text-file "environment"
  261. (append-map (match-lambda
  262. ((key . value)
  263. (list key "=" value "\n")))
  264. vars)))
  265. (define session-environment-service-type
  266. (service-type
  267. (name 'session-environment)
  268. (extensions
  269. (list (service-extension
  270. etc-service-type
  271. (lambda (vars)
  272. (list `("environment"
  273. ,(environment-variables->environment-file vars)))))))
  274. (compose concatenate)
  275. (extend append)
  276. (description
  277. "Populate @file{/etc/environment}, which is honored by @code{pam_env},
  278. with the specified environment variables. The value of this service is a list
  279. of name/value pairs for environments variables, such as:
  280. @example
  281. '((\"TZ\" . \"Canada/Pacific\"))
  282. @end example\n")))
  283. (define (session-environment-service vars)
  284. "Return a service that builds the @file{/etc/environment}, which can be read
  285. by PAM-aware applications to set environment variables for sessions.
  286. VARS should be an association list in which both the keys and the values are
  287. strings or string-valued gexps."
  288. (service session-environment-service-type vars))
  289. ;;;
  290. ;;; PAM root service.
  291. ;;;
  292. ;; Overall PAM configuration: a list of services, plus a procedure that takes
  293. ;; one <pam-service> and returns a <pam-service>. The procedure is used to
  294. ;; implement cross-cutting concerns such as the use of the 'elogind.so'
  295. ;; session module that keeps track of logged-in users.
  296. (define-record-type* <pam-configuration>
  297. pam-configuration make-pam-configuration? pam-configuration?
  298. (services pam-configuration-services) ;list of <pam-service>
  299. (transform pam-configuration-transform)) ;procedure
  300. (define (/etc-entry config)
  301. "Return the /etc/pam.d entry corresponding to CONFIG."
  302. (match config
  303. (($ <pam-configuration> services transform)
  304. (let ((services (map transform services)))
  305. `(("pam.d" ,(pam-services->directory services)))))))
  306. (define (extend-configuration initial extensions)
  307. "Extend INITIAL with NEW."
  308. (let-values (((services procs)
  309. (partition pam-service? extensions)))
  310. (pam-configuration
  311. (services (append (pam-configuration-services initial)
  312. services))
  313. (transform (apply compose
  314. (pam-configuration-transform initial)
  315. procs)))))
  316. (define pam-root-service-type
  317. (service-type (name 'pam)
  318. (extensions (list (service-extension etc-service-type
  319. /etc-entry)))
  320. ;; Arguments include <pam-service> as well as procedures.
  321. (compose concatenate)
  322. (extend extend-configuration)))
  323. (define* (pam-root-service base #:key (transform identity))
  324. "The \"root\" PAM service, which collects <pam-service> instance and turns
  325. them into a /etc/pam.d directory, including the <pam-service> listed in BASE.
  326. TRANSFORM is a procedure that takes a <pam-service> and returns a
  327. <pam-service>. It can be used to implement cross-cutting concerns that affect
  328. all the PAM services."
  329. (service pam-root-service-type
  330. (pam-configuration (services base)
  331. (transform transform))))