[bug#72316,v2,3/3] Switch to Guile-PAM.

Message ID 3ce2c97a2c23ba3eb0a9ff4544e686048fc46174.1746104902.git.felix.lechner@lease-up.com
State New
Headers
Series [bug#72316,v2,1/3] Add guile-pam. |

Commit Message

Felix Lechner May 1, 2025, 1:42 p.m. UTC
  Change-Id: Ib691b41cdb152f508a4a8d1b12b2a20da8706fed
---
 gnu/services/authentication.scm |   9 +-
 gnu/services/base.scm           |  16 +-
 gnu/services/desktop.scm        |  14 +-
 gnu/services/kerberos.scm       |  12 +-
 gnu/services/lightdm.scm        |  69 ++++++--
 gnu/services/pam-mount.scm      |   5 +-
 gnu/services/sddm.scm           |  91 +++++++---
 gnu/services/xorg.scm           |  17 +-
 gnu/system/pam.scm              | 296 ++++++++++++++++++++++++++------
 9 files changed, 420 insertions(+), 109 deletions(-)
  

Comments

Maxim Cournoyer May 1, 2025, 2:38 p.m. UTC | #1
Hi Felix,

Felix Lechner <felix.lechner@lease-up.com> writes:

> Change-Id: Ib691b41cdb152f508a4a8d1b12b2a20da8706fed

Missing GNU changelog.  It seems there's already an agreement to
postpone on this "big" change, until the service and documentation had
some time to sink in, shake bugs (if any), and gather user feedback?
That would be wise, I think.

In the meantime, at least the patch would need reworking so the code
fits our 80 columns maximum width convention.

I'd defer this one until a later time, and present it with a GCD when
the moment is ripe?
  

Patch

diff --git a/gnu/services/authentication.scm b/gnu/services/authentication.scm
index fbfef2d3d0a..88ccba6ada4 100644
--- a/gnu/services/authentication.scm
+++ b/gnu/services/authentication.scm
@@ -503,9 +503,6 @@  (define (nslcd-shepherd-service config)
 
 (define (pam-ldap-pam-service config)
   "Return a PAM service for LDAP authentication."
-  (define pam-ldap-module
-    (file-append (nslcd-configuration-nss-pam-ldapd config)
-                     "/lib/security/pam_ldap.so"))
   (pam-extension
     (transformer
      (lambda (pam)
@@ -514,7 +511,11 @@  (define (pam-ldap-pam-service config)
            (let ((sufficient
                   (pam-entry
                    (control "sufficient")
-                   (module pam-ldap-module))))
+                   (module "pam_ldap.so")
+                   (foreign-library-path
+                    (list
+                     (file-append (nslcd-configuration-nss-pam-ldapd config)
+                                  "/lib/security"))))))
              (pam-service
               (inherit pam)
               (auth (cons sufficient (pam-service-auth pam)))
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index 650121be8f1..6fb84ce01e1 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -61,8 +61,8 @@  (define-module (gnu services base)
   #:use-module (gnu packages admin)
   #:use-module ((gnu packages linux)
                 #:select (alsa-utils btrfs-progs crda eudev
-                          e2fsprogs f2fs-tools fuse gpm kbd lvm2 rng-tools
-                          util-linux xfsprogs))
+                          e2fsprogs f2fs-tools fuse gpm kbd linux-pam
+                          lvm2 rng-tools util-linux xfsprogs))
   #:use-module (gnu packages bash)
   #:use-module ((gnu packages base)
                 #:select (coreutils glibc glibc/hurd
@@ -1731,7 +1731,10 @@  (define pam-limits-service-type
                                   (control "required")
                                   (module "pam_limits.so")
                                   (arguments
-                                   (list #~(string-append "conf=" #$limits-file))))))
+                                   (list #~(string-append "conf=" #$limits-file)))
+                                  (foreign-library-path
+                                   (list
+                                    (file-append linux-pam "/lib/security"))))))
                  (if (member (pam-service-name pam)
                              '("login" "greetd" "su" "slim" "gdm-password"
                                "sddm" "lightdm" "sudo" "sshd"))
@@ -4032,8 +4035,11 @@  (define (greetd-pam-service config)
   (define optional-pam-mount
     (pam-entry
      (control "optional")
-     (module (file-append greetd-pam-mount "/lib/security/pam_mount.so"))
-     (arguments '("disable_interactive"))))
+     (module "pam_mount.so")
+     (arguments '("disable_interactive"))
+     (foreign-library-path
+      (list
+       (file-append greetd-pam-mount "/lib/security")))))
 
   (list
    (unix-pam-service "greetd"
diff --git a/gnu/services/desktop.scm b/gnu/services/desktop.scm
index a586746cc59..ca1078f9df6 100644
--- a/gnu/services/desktop.scm
+++ b/gnu/services/desktop.scm
@@ -1494,8 +1494,10 @@  (define (pam-extension-procedure config)
   (define pam-elogind
     (pam-entry
      (control "required")
-     (module (file-append (elogind-configuration-elogind config)
-                          "/lib/security/pam_elogind.so"))))
+     (module "pam_elogind.so")
+     (foreign-library-path
+      (list
+       (file-append (elogind-configuration-elogind config) "/lib/security")))))
 
   (list (pam-extension
          (transformer
@@ -1712,9 +1714,11 @@  (define (pam-gnome-keyring config)
        (define (%pam-keyring-entry . arguments)
          (pam-entry
           (control "optional")
-          (module (file-append (gnome-keyring-package config)
-                               "/lib/security/pam_gnome_keyring.so"))
-          (arguments arguments)))
+          (module ("pam_gnome_keyring.so"))
+          (arguments arguments)
+          (foreign-library-path
+           (list
+            (file-append (gnome-keyring-package config) "/lib/security")))))
 
        (list
         (pam-extension
diff --git a/gnu/services/kerberos.scm b/gnu/services/kerberos.scm
index a6f540a9b6a..d2d8988a837 100644
--- a/gnu/services/kerberos.scm
+++ b/gnu/services/kerberos.scm
@@ -431,18 +431,18 @@  (define (pam-krb5-pam-service config)
   (pam-extension
    (transformer
     (lambda (pam)
-      (define pam-krb5-module
-        (file-append (pam-krb5-configuration-pam-krb5 config)
-                     "/lib/security/pam_krb5.so"))
-
       (let ((pam-krb5-sufficient
              (pam-entry
               (control "sufficient")
-              (module pam-krb5-module)
+              (module "pam_krb5.so")
               (arguments
                (list
                 (format #f "minimum_uid=~a"
-                        (pam-krb5-configuration-minimum-uid config)))))))
+                        (pam-krb5-configuration-minimum-uid config))))
+              (foreign-library-path
+               (list
+                (file-append (pam-krb5-configuration-pam-krb5 config)
+                             "/lib/security"))))))
         (pam-service
          (inherit pam)
          (auth (cons* pam-krb5-sufficient
diff --git a/gnu/services/lightdm.scm b/gnu/services/lightdm.scm
index b69cc21322a..6cca06b827a 100644
--- a/gnu/services/lightdm.scm
+++ b/gnu/services/lightdm.scm
@@ -24,6 +24,7 @@  (define-module (gnu services lightdm)
   #:use-module (gnu packages display-managers)
   #:use-module (gnu packages freedesktop)
   #:use-module (gnu packages gnome)
+  #:use-module ((gnu packages linux) #:select (linux-pam))
   #:use-module (gnu packages vnc)
   #:use-module (gnu packages xorg)
   #:use-module (gnu services configuration)
@@ -546,15 +547,35 @@  (define (lightdm-greeter-pam-service)
    (name "lightdm-greeter")
    (auth (list
           ;; Load environment from /etc/environment and ~/.pam_environment.
-          (pam-entry (control "required") (module "pam_env.so"))
+          (pam-entry (control "required")
+                     (module "pam_env.so")
+                     (foreign-library-path
+                      (list
+                       (file-append linux-pam "/lib/security"))))
           ;; Always let the greeter start without authentication.
-          (pam-entry (control "required") (module "pam_permit.so"))))
+          (pam-entry (control "required")
+                     (module "pam_permit.so")
+                     (foreign-library-path
+                      (list
+                       (file-append linux-pam "/lib/security"))))))
    ;; No action required for account management
-   (account (list (pam-entry (control "required") (module "pam_permit.so"))))
+   (account (list (pam-entry (control "required")
+                             (module "pam_permit.so")
+                             (foreign-library-path
+                              (list
+                               (file-append linux-pam "/lib/security"))))))
    ;; Prohibit changing password.
-   (password (list (pam-entry (control "required") (module "pam_deny.so"))))
+   (password (list (pam-entry (control "required")
+                              (module "pam_deny.so")
+                              (foreign-library-path
+                               (list
+                                (file-append linux-pam "/lib/security"))))))
    ;; Setup session.
-   (session (list (pam-entry (control "required") (module "pam_unix.so"))))))
+   (session (list (pam-entry (control "required")
+                             (module "pam_unix.so")
+                             (foreign-library-path
+                              (list
+                               (file-append linux-pam "/lib/security"))))))))
 
 (define (lightdm-autologin-pam-service)
   "Return a PAM service for @command{lightdm-autologin}}."
@@ -563,17 +584,41 @@  (define (lightdm-autologin-pam-service)
    (auth
     (list
      ;; Block login if user is globally disabled.
-     (pam-entry (control "required") (module "pam_nologin.so"))
-     (pam-entry (control "required") (module "pam_succeed_if.so")
-                (arguments (list "uid >= 1000")))
+     (pam-entry (control "required")
+                (module "pam_nologin.so")
+                (foreign-library-path
+                 (list
+                  (file-append linux-pam "/lib/security"))))
+     (pam-entry (control "required")
+                (module "pam_succeed_if.so")
+                (arguments (list "uid >= 1000"))
+                (foreign-library-path
+                 (list
+                  (file-append linux-pam "/lib/security"))))
      ;; Allow access without authentication.
-     (pam-entry (control "required") (module "pam_permit.so"))))
+     (pam-entry (control "required")
+                (module "pam_permit.so")
+                (foreign-library-path
+                 (list
+                  (file-append linux-pam "/lib/security"))))))
    ;; Stop autologin if account requires action.
-   (account (list (pam-entry (control "required") (module "pam_unix.so"))))
+   (account (list (pam-entry (control "required")
+                             (module "pam_unix.so")
+                             (foreign-library-path
+                              (list
+                               (file-append linux-pam "/lib/security"))))))
    ;; Prohibit changing password.
-   (password (list (pam-entry (control "required") (module "pam_deny.so"))))
+   (password (list (pam-entry (control "required")
+                              (module "pam_deny.so")
+                              (foreign-library-path
+                               (list
+                                (file-append linux-pam "/lib/security"))))))
    ;; Setup session.
-   (session (list (pam-entry (control "required") (module "pam_unix.so"))))))
+   (session (list (pam-entry (control "required")
+                             (module "pam_unix.so")
+                             (foreign-library-path
+                              (list
+                               (file-append linux-pam "/lib/security"))))))))
 
 (define (lightdm-pam-services config)
   (list (lightdm-pam-service config)
diff --git a/gnu/services/pam-mount.scm b/gnu/services/pam-mount.scm
index b3a02e82e92..1eb5b44e315 100644
--- a/gnu/services/pam-mount.scm
+++ b/gnu/services/pam-mount.scm
@@ -94,7 +94,10 @@  (define (pam-mount-pam-service config)
   (define optional-pam-mount
     (pam-entry
      (control "optional")
-     (module (file-append pam-mount "/lib/security/pam_mount.so"))))
+     (module "pam_mount.so")
+     (foreign-library-path
+      (list
+       (file-append pam-mount "/lib/security")))))
   (list
    (pam-extension
     (transformer
diff --git a/gnu/services/sddm.scm b/gnu/services/sddm.scm
index 92d64cc5993..cb2c5a9276d 100644
--- a/gnu/services/sddm.scm
+++ b/gnu/services/sddm.scm
@@ -24,6 +24,7 @@  (define-module (gnu services sddm)
   #:use-module (gnu packages admin)
   #:use-module (gnu packages display-managers)
   #:use-module (gnu packages freedesktop)
+  #:use-module ((gnu packages linux) #:select (linux-pam))
   #:use-module (gnu packages xorg)
   #:use-module (gnu services)
   #:use-module (gnu services shepherd)
@@ -206,40 +207,61 @@  (define (sddm-pam-service config)
     (list
      (pam-entry
       (control "requisite")
-      (module "pam_nologin.so"))
+      (module "pam_nologin.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))
      (pam-entry
       (control "required")
-      (module "pam_env.so"))
+      (module "pam_env.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))
      (pam-entry
       (control "required")
       (module "pam_succeed_if.so")
       (arguments (list (string-append "uid >= "
                                       (number->string (sddm-configuration-minimum-uid config)))
-                       "quiet")))
+                       "quiet"))
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))
      ;; should be factored out into system-auth
      (pam-entry
       (control "required")
-      (module "pam_unix.so"))))
+      (module "pam_unix.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))))
    (account
     (list
      ;; should be factored out into system-account
      (pam-entry
       (control "required")
-      (module "pam_unix.so"))))
+      (module "pam_unix.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))))
    (password
     (list
      ;; should be factored out into system-password
      (pam-entry
       (control "required")
       (module "pam_unix.so")
-      (arguments (list "sha512" "shadow" "try_first_pass")))))
+      (arguments (list "sha512" "shadow" "try_first_pass"))
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))))
    (session
     (list
      ;; lfs has a required pam_limits.so
      ;; should be factored out into system-session
      (pam-entry
       (control "required")
-      (module "pam_unix.so"))))))
+      (module "pam_unix.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))))))
 
 (define (sddm-greeter-pam-service)
   "Return a PAM service for @command{sddm-greeter}."
@@ -250,29 +272,44 @@  (define (sddm-greeter-pam-service)
      ;; Load environment from /etc/environment and ~/.pam_environment
      (pam-entry
       (control "required")
-      (module "pam_env.so"))
+      (module "pam_env.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))
      ;; Always let the greeter start without authentication
      (pam-entry
       (control "required")
-      (module "pam_permit.so"))))
+      (module "pam_permit.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))))
    (account
     (list
      ;; No action required for account management
      (pam-entry
       (control "required")
-      (module "pam_permit.so"))))
+      (module "pam_permit.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))))
    (password
     (list
      ;; Can't change password
      (pam-entry
       (control "required")
-      (module "pam_deny.so"))))
+      (module "pam_deny.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))))
    (session
     (list
      ;; Setup session
      (pam-entry
       (control "required")
-      (module "pam_unix.so"))))))
+      (module "pam_unix.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))))))
 
 (define (sddm-autologin-pam-service config)
   "Return a PAM service for @command{sddm-autologin}"
@@ -282,31 +319,37 @@  (define (sddm-autologin-pam-service config)
     (list
      (pam-entry
       (control "requisite")
-      (module "pam_nologin.so"))
+      (module "pam_nologin.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))
      (pam-entry
       (control "required")
       (module "pam_succeed_if.so")
       (arguments (list (string-append "uid >= "
                                       (number->string (sddm-configuration-minimum-uid config)))
-                       "quiet")))
+                       "quiet"))
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))
      (pam-entry
       (control "required")
-      (module "pam_permit.so"))))
+      (module "pam_permit.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))))
    (account
-    (list
-     (pam-entry
-      (control "include")
-      (module "sddm"))))
+    (pam-service-account (sddm-pam-service config)))
    (password
     (list
      (pam-entry
       (control "required")
-      (module "pam_deny.so"))))
+      (module "pam_deny.so")
+      (foreign-library-path
+       (list
+        (file-append linux-pam "/lib/security"))))))
    (session
-    (list
-     (pam-entry
-      (control "include")
-      (module "sddm"))))))
+    (pam-service-session (sddm-pam-service config)))))
 
 (define (sddm-pam-services config)
   (list (sddm-pam-service config)
diff --git a/gnu/services/xorg.scm b/gnu/services/xorg.scm
index bef05b9bb9b..21f9924d166 100644
--- a/gnu/services/xorg.scm
+++ b/gnu/services/xorg.scm
@@ -1236,16 +1236,25 @@  (define (gdm-pam-service config)
                                #:login-uid? #t))
     (auth (list (pam-entry
                  (control "optional")
-                 (module (file-append (gdm-configuration-gdm config)
-                                      "/lib/security/pam_gdm.so")))
+                 (module "pam_gdm.so")
+                 (foreign-library-path
+                  (list
+                   (file-append (gdm-configuration-gdm config)
+                                "/lib/security/"))))
                 (pam-entry
                  (control "sufficient")
-                 (module "pam_permit.so")))))
+                 (module "pam_permit.so")
+                 (foreign-library-path
+                  (list
+                   (file-append linux-pam "/lib/security")))))))
    (pam-service
     (inherit (unix-pam-service "gdm-launch-environment"))
     (auth (list (pam-entry
                  (control "required")
-                 (module "pam_permit.so")))))
+                 (module "pam_permit.so")
+                 (foreign-library-path
+                  (list
+                   (file-append linux-pam "/lib/security")))))))
    (unix-pam-service "gdm-password"
                      #:login-uid? #t
                      #:allow-empty-passwords?
diff --git a/gnu/system/pam.scm b/gnu/system/pam.scm
index 07b84b04efe..08dc4693297 100644
--- a/gnu/system/pam.scm
+++ b/gnu/system/pam.scm
@@ -32,7 +32,9 @@  (define-module (gnu system pam)
   #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-26)
   #:use-module ((guix utils) #:select (%current-system))
+  #:use-module (gnu packages guile)
   #:use-module (gnu packages linux)
+  #:use-module (gnu packages mes)
   #:export (pam-service
             pam-service?
             pam-service-name
@@ -46,6 +48,8 @@  (define-module (gnu system pam)
             pam-entry-control
             pam-entry-module
             pam-entry-arguments
+            pam-entry-guile-inputs
+            pam-entry-foreign-library-path
 
             pam-limits-entry
             pam-limits-entry?
@@ -95,10 +99,16 @@  (define-record-type* <pam-service> pam-service
 (define-record-type* <pam-entry> pam-entry
   make-pam-entry
   pam-entry?
-  (control    pam-entry-control)         ; string
+  (control    pam-entry-control)         ; string, symbol or g-expression
   (module     pam-entry-module)          ; file name
   (arguments  pam-entry-arguments        ; list of string-valued g-expressions
-              (default '())))
+              (default '()))
+  (guile-inputs pam-entry-guile-inputs   ; list of package variables
+                (default '()))
+  (foreign-library-path pam-entry-foreign-library-path ; list of file-like folders
+                        ;; courtesy for historical usage
+                        (default (list
+                                  (file-append linux-pam "/lib/security")))))
 
 ;; 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
@@ -153,35 +163,80 @@  (define (pam-limits-entry->string entry)
                           (number->string value))))
                   "	"))))
 
-(define (pam-service->configuration service)
+(define (pam-service->configuration service shared-object environment-file pamda-file)
   "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)
+  (mixed-text-file (pam-service-name service)
+                   "account  required " shared-object " " environment-file " " pamda-file "\n"
+                   "auth     required " shared-object " " environment-file " " pamda-file "\n"
+                   "password required " shared-object " " environment-file " " pamda-file "\n"
+                   "session  required " shared-object " " environment-file " " pamda-file "\n"))
+
+(define (intersperse a xs)
+  (if (null? xs)
+      '()
+      [cons (car xs)
+            (if (null? (cdr xs))
+                (cdr xs)
+                (cons a (intersperse a (cdr xs))))]))
+
+;; should include locale, but keeping effects of pam_env.so
+(define* (make-environment-file #:key
+                                (guile-auto-compile? #f)
+                                (guile-extensions-path '())
+                                (guile-install-locale? #f)
+                                (guile-jit-log-level 0)
+                                (guile-jit-pause-when-stopping? #f)
+                                (guile-jit-stop-after -1)
+                                (guile-jit-threshold 1000)
+                                (guile-load-compiled-path '())
+                                (guile-load-path '())
+                                (guile-warn-deprecated "yes")
+                                (ld-library-path '())
+                                (ltdl-library-path '()))
+  (let* ((lines `(("GUILE_AUTO_COMPILE=" ,(if guile-auto-compile? "1" "0"))
+                  ("GUILE_EXTENSIONS_PATH=" ,@(intersperse ":" guile-extensions-path))
+                  ("GUILE_INSTALL_LOCALE=" ,(if guile-install-locale? "1" "0"))
+                  ("GUILE_JIT_LOG=" ,(number->string guile-jit-log-level))
+                  ("GUILE_JIT_PAUSE_WHEN_STOPPING=" ,(if guile-jit-pause-when-stopping? "1" "0"))
+                  ("GUILE_JIT_STOP_AFTER=" ,(number->string guile-jit-stop-after))
+                  ("GUILE_JIT_THRESHOLD=" ,(number->string guile-jit-threshold))
+                  ("GUILE_LOAD_COMPILED_PATH=" ,@(intersperse ":" guile-load-compiled-path))
+                  ("GUILE_LOAD_PATH=" ,@(intersperse ":" guile-load-path))
+                  ("GUILE_WARN_DEPRECATED=" ,guile-warn-deprecated)
+                  ("LD_LIBRARY_PATH=" ,@(intersperse ":" ld-library-path))
+                  ("LTDL_LIBRARY_PATH=" ,@(intersperse ":" ltdl-library-path))))
+         (terminated (map (lambda (line)
+                            (append line '("\0")))
+                          lines))
+         (flattened (fold (lambda (right left)
+                            (append left right))
+                          '()
+                          terminated)))
+    (apply mixed-text-file "guile-pam-environment" flattened)))
+
+(define (pam-services->directory shared-object
+                                 guile-inputs
+                                 foreign-library-path
+                                 folder
+                                 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)))
+  (let* ((names (map pam-service-name services))
+         (load-path (map (lambda (package)
+                           (file-append package "/share/guile/site/3.0"))
+                         guile-inputs))
+         (load-compiled-path (map (lambda (package)
+                                    (file-append package "/lib/guile/3.0/site-ccache"))
+                                  guile-inputs))
+         (environment-file (make-environment-file #:guile-extensions-path foreign-library-path
+                                                  #:ld-library-path foreign-library-path
+                                                  #:guile-load-path load-path
+                                                  #:guile-load-compiled-path load-compiled-path))
+         (pamda-file (make-pam-stack folder services))
+         (files (map (cut pam-service->configuration <>
+                          shared-object environment-file pamda-file)
+                     services)))
     (define builder
       #~(begin
           (use-modules (ice-9 match)
@@ -198,14 +253,17 @@  (define (pam-services->directory services)
                     ;; instead.  See <http://bugs.gnu.org/20037>.
                     (delete-duplicates '#$(zip names files)))))
 
-    (computed-file "pam.d" builder)))
+    (computed-file folder 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"))))
+               (module "pam_deny.so")
+               (foreign-library-path
+                (list
+                 (file-append linux-pam "/lib/security"))))))
     (pam-service
      (name "other")
      (account (list deny))
@@ -216,12 +274,18 @@  (define %pam-other-services
 (define unix-pam-service
   (let ((unix (pam-entry
                (control "required")
-               (module "pam_unix.so")))
+               (module "pam_unix.so")
+               (foreign-library-path
+                (list
+                 (file-append linux-pam "/lib/security")))))
         (env  (pam-entry ; to honor /etc/environment.
                (control "required")
-               (module "pam_env.so"))))
+               (module "pam_env.so")
+               (foreign-library-path
+                (list
+                 (file-append linux-pam "/lib/security"))))))
     (lambda* (name #:key allow-empty-passwords? allow-root? motd
-              login-uid? gnupg?)
+                   login-uid? gnupg?)
       "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
@@ -237,40 +301,61 @@  (define unix-pam-service
        (auth (append (if allow-root?
                          (list (pam-entry
                                 (control "sufficient")
-                                (module "pam_rootok.so")))
+                                (module "pam_rootok.so")
+                                (foreign-library-path
+                                 (list
+                                  (file-append linux-pam "/lib/security")))))
                          '())
                      (list (if allow-empty-passwords?
                                (pam-entry
                                 (control "required")
                                 (module "pam_unix.so")
-                                (arguments '("nullok")))
+                                (arguments '("nullok"))
+                                (foreign-library-path
+                                 (list
+                                  (file-append linux-pam "/lib/security"))))
                                unix))
                      (if gnupg?
                          (list (pam-entry
                                 (control "required")
-                                (module (file-append pam-gnupg "/lib/security/pam_gnupg.so"))))
+                                (module "pam_gnupg.so")
+                                (foreign-library-path
+                                 (list
+                                  (file-append pam-gnupg "/lib/security")))))
                          '())))
        (password (list (pam-entry
                         (control "required")
                         (module "pam_unix.so")
                         ;; Store SHA-512 encrypted passwords in /etc/shadow.
-                        (arguments '("sha512" "shadow")))))
+                        (arguments '("sha512" "shadow"))
+                        (foreign-library-path
+                         (list
+                          (file-append linux-pam "/lib/security"))))))
        (session `(,@(if motd
                         (list (pam-entry
                                (control "optional")
                                (module "pam_motd.so")
                                (arguments
-                                (list #~(string-append "motd=" #$motd)))))
+                                (list #~(string-append "motd=" #$motd)))
+                               (foreign-library-path
+                                (list
+                                 (file-append linux-pam "/lib/security")))))
                         '())
                   ,@(if login-uid?
                         (list (pam-entry       ;to fill in /proc/self/loginuid
                                (control "required")
-                               (module "pam_loginuid.so")))
+                               (module "pam_loginuid.so")
+                               (foreign-library-path
+                                (list
+                                 (file-append linux-pam "/lib/security")))))
                         '())
                   ,@(if gnupg?
                         (list (pam-entry
                                (control "required")
-                               (module (file-append pam-gnupg "/lib/security/pam_gnupg.so"))))
+                               (module "pam_gnupg.so")
+                               (foreign-library-path
+                                (list
+                                 (file-append pam-gnupg "/lib/security")))))
                         '())
                   ,env ,unix))))))
 
@@ -279,13 +364,19 @@  (define (rootok-pam-service command)
 authenticate to run COMMAND."
   (let ((unix (pam-entry
                (control "required")
-               (module "pam_unix.so"))))
+               (module "pam_unix.so")
+               (foreign-library-path
+                (list
+                 (file-append linux-pam "/lib/security"))))))
     (pam-service
      (name command)
      (account (list unix))
      (auth (list (pam-entry
                   (control "sufficient")
-                  (module "pam_rootok.so"))))
+                  (module "pam_rootok.so")
+                  (foreign-library-path
+                   (list
+                    (file-append linux-pam "/lib/security"))))))
      (password (list unix))
      (session (list unix)))))
 
@@ -377,21 +468,114 @@  (define-record-type* <pam-configuration>
   (services  pam-configuration-services)
   ;list of procedures <pam-entry> -> <pam-entry>
   (transformers pam-configuration-transformers)
+  ;; file-like shared module
+  (shared-object pam-configuration-shared-object)
+  ;; list of package variables
+  (guile-inputs pam-configuration-guile-inputs)
+  ;; list of file-like folders
+  (foreign-library-path pam-configuration-foreign-library-path)
   ;list of symbols
   (shepherd-requirements pam-configuration-shepherd-requirements))
 
+(define (make-pam-stack folder services)
+  (define* (entry->gate entry
+                        #:key
+                        only-actions
+                        only-services)
+    (match entry
+      (($ <pam-entry> control module (options ...))
+       ;; adapted from (pam legacy configuration)
+       (cond
+        ((string=? "include" control)
+         (error "PAM include not implemented; send list of <pam-entry> instead"
+                control module options entry))
+        ((string=? "substack" control)
+         ;; this probably differs a little bit from Linux-PAM
+         #~(gate required (stack-pamda
+                           (configuration-file->gates #$folder #$module
+                                                      #:only-actions '#$only-actions
+                                                      #:only-services '#$only-services))
+                 #:only-actions '#$only-actions
+                 #:only-services '#$only-services))
+        (else
+         #~(gate (control-string->plan #$control)
+                 (shared-object-or-pamda #$module)
+                 #:options (list #$@options)
+                 #:only-actions '#$only-actions
+                 #:only-services '#$only-services))))))
+
+  (define (service->gates service)
+    (match service
+      (($ <pam-service> name account auth password session)
+       (append (map (cut entry->gate <>
+                         #:only-actions '(pam_sm_acct_mgmt)
+                         #:only-services (list name))
+                    account)
+               (map (cut entry->gate <>
+                         #:only-actions '(pam_sm_authenticate
+                                          pam_sm_setcred)
+                         #:only-services (list name))
+                    auth)
+               (map (cut entry->gate <>
+                         #:only-actions '(pam_sm_chauthtok)
+                         #:only-services (list name))
+                    password)
+               (map (cut entry->gate <>
+                         #:only-actions '(pam_sm_open_session
+                                          pam_sm_close_session)
+                         #:only-services (list name))
+                    session)))))
+
+  (let* ((gates (append-map service->gates services)))
+    (scheme-file
+     "guile-pam-stack.scm"
+     #~(begin
+         (use-modules (pam stack)
+                      (pam legacy configuration)
+                      (pam legacy module)
+                      (pam legacy stack))
+         (stack-pamda (list #$@gates))))))
+
 (define (/etc-entry config)
   "Return the /etc/pam.d entry corresponding to CONFIG."
+  (define (service->pam-entries service)
+    (match service
+      (($ <pam-service> name account auth password session)
+       (append account auth password session))))
   (match config
-    (($ <pam-configuration> services transformers shepherd-requirements)
-     (let ((services (map (apply compose identity transformers)
-                          services)))
-       `(("pam.d" ,(pam-services->directory services)))))))
+    (($ <pam-configuration> services
+                            transformers
+                            shared-object
+                            guile-inputs
+                            foreign-library-path
+                            shepherd-requirements)
+     (let* ((services (map (apply compose identity transformers)
+                           services))
+            (all-entries (append-map service->pam-entries
+                                     services))
+            (combined-inputs (delete-duplicates
+                              (append guile-inputs
+                                      (append-map pam-entry-guile-inputs
+                                                  all-entries))))
+            (combined-library-path (delete-duplicates
+                                    (append foreign-library-path
+                                            (append-map pam-entry-foreign-library-path
+                                                        all-entries)))))
+       `(("pam.d" ,(pam-services->directory shared-object
+                                            combined-inputs
+                                            combined-library-path
+                                            "pam.d"
+                                            services)))))))
 
 (define (pam-shepherd-service config)
   "Return the PAM synchronization shepherd service corresponding to CONFIG."
   (match config
-    (($ <pam-configuration> services transformers shepherd-requirements)
+    (($ <pam-configuration> services
+                            transformers
+                            shared-object
+                            guile-inputs
+                            foreign-library-path
+                            shepherd-requirements)
      (list (shepherd-service
             (documentation "Synchronization point for services that need to be
 started for PAM to work.")
@@ -420,6 +604,9 @@  (define (extend-configuration initial extensions)
                        services))
      (transformers (append (pam-configuration-transformers initial)
                            (map pam-extension-transformer pam-extensions)))
+     (shared-object (pam-configuration-shared-object initial))
+     (guile-inputs (pam-configuration-guile-inputs initial))
+     (foreign-library-path (pam-configuration-foreign-library-path initial))
      (shepherd-requirements
       (append (pam-configuration-shepherd-requirements initial)
               (append-map pam-extension-shepherd-requirements pam-extensions))))))
@@ -445,8 +632,18 @@  (define pam-root-service-type
 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 (transformers '()) (shepherd-requirements '()))
+(define* (pam-root-service base
+                           #:key
+                           (transformers '())
+                           (shared-object
+                            (file-append guile-pam "/lib/security/pam_guile.so"))
+                           (guile-inputs
+                            (list guile-3.0
+                                  guile-pam ;for (pam) and (ffi pam)
+                                  nyacc-2.01)) ;for (nyacc foreign cdata)
+                           (foreign-library-path
+                            (list (file-append linux-pam "/lib"))) ;for libpam.so
+                           (shepherd-requirements '()))
   "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
@@ -455,6 +652,9 @@  (define* (pam-root-service base #:key (transformers '()) (shepherd-requirements
   (service pam-root-service-type
            (pam-configuration (services base)
                               (transformers transformers)
+                              (shared-object shared-object)
+                              (guile-inputs guile-inputs)
+                              (foreign-library-path foreign-library-path)
                               (shepherd-requirements shepherd-requirements))))