From 58fc96bb716cdd5ec17796914283720ef9f28240 Mon Sep 17 00:00:00 2001
From: Clayton Craft <clayton@craftyguy.net>
Date: Sat, 23 May 2026 17:42:55 -0700
Subject: [PATCH 1/3] pam_systemd: add inhibit= and inhibit-why= module
 arguments to parse_argv

This adds two new module arguments to parse_argv:

1) inhibit= takes a colon-separated list of inhibitor lock types to acquire

2) inhibit-why= takes an optional human-readable reason string.

Both are currently parsed but are unused until a later commit.
---
 src/login/pam_systemd.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c
index 9bdd147db4d08..aeb917977bdbf 100644
--- a/src/login/pam_systemd.c
+++ b/src/login/pam_systemd.c
@@ -117,6 +117,8 @@ static int parse_argv(
                 const char **type,
                 const char **desktop,
                 const char **area,
+                const char **inhibit_what,
+                const char **inhibit_why,
                 bool *debug,
                 uint64_t *default_capability_bounding_set,
                 uint64_t *default_capability_ambient_set) {
@@ -148,6 +150,12 @@ static int parse_argv(
                                 sym_pam_syslog(pamh, LOG_WARNING, "Area name specified among PAM module parameters is not valid, ignoring: %s", p);
                         else if (area)
                                 *area = p;
+                } else if ((p = startswith(argv[i], "inhibit="))) {
+                        if (inhibit_what)
+                                *inhibit_what = p;
+                } else if ((p = startswith(argv[i], "inhibit-why="))) {
+                        if (inhibit_why)
+                                *inhibit_why = p;
 
                 } else if (streq(argv[i], "debug")) {
                         if (debug)
@@ -1755,7 +1763,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
         pam_log_setup();
 
         uint64_t default_capability_bounding_set = CAP_MASK_UNSET, default_capability_ambient_set = CAP_MASK_UNSET;
-        const char *class_pam = NULL, *type_pam = NULL, *desktop_pam = NULL, *area_pam = NULL;
+        const char *class_pam = NULL, *type_pam = NULL, *desktop_pam = NULL, *area_pam = NULL, *inhibit_what = NULL, *inhibit_why = NULL;
         bool debug = false;
         if (parse_argv(pamh,
                        argc, argv,
@@ -1763,6 +1771,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
                        &type_pam,
                        &desktop_pam,
                        &area_pam,
+                       &inhibit_what,
+                       &inhibit_why,
                        &debug,
                        &default_capability_bounding_set,
                        &default_capability_ambient_set) < 0)
@@ -1859,6 +1869,8 @@ _public_ PAM_EXTERN int pam_sm_close_session(
                        /* type= */ NULL,
                        /* desktop= */ NULL,
                        /* area= */ NULL,
+                       /* inhibit_what= */ NULL,
+                       /* inhibit_why= */ NULL,
                        &debug,
                        /* default_capability_bounding_set= */ NULL,
                        /* default_capability_ambient_set= */ NULL) < 0)

From 179b7c2ab5491e7772c51acc33abd0a991be9851 Mon Sep 17 00:00:00 2001
From: Clayton Craft <clayton@craftyguy.net>
Date: Sat, 23 May 2026 20:06:31 -0700
Subject: [PATCH 2/3] pam_systemd: acquire inhibit lock when inhibit= is
 specified

This adds a new function, acquire_inhibit_lock, that takes a logind
inhibitor lock for the duration of the PAM session. The lock fd is
stored in PAM data with pam_cleanup_close. In pam_sm_close_session, the
lock is released explicitly by passing NULL to pam_set_data, which
triggers the registered cleanup function immediately rather than waiting
for pam_end.

The inhibit type and reason string can also be configured via the
XDG_SESSION_INHIBIT and XDG_SESSION_INHIBIT_WHY environment variables,
which take precedence over the module arguments.

Fixes: #20654
---
 src/login/pam_systemd.c | 60 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c
index aeb917977bdbf..06d19607b2f9c 100644
--- a/src/login/pam_systemd.c
+++ b/src/login/pam_systemd.c
@@ -1747,6 +1747,57 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) {
         return PAM_SUCCESS;
 }
 
+static int acquire_inhibit_lock(
+                pam_handle_t *pamh,
+                const char *inhibit_what,
+                const char *inhibit_why,
+                bool debug) {
+
+        _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        const char *service = NULL;
+        int r;
+
+        assert(pamh);
+        assert(inhibit_what);
+
+        r = pam_acquire_bus_connection(pamh, "pam-systemd", debug, &bus, &d);
+        if (r != PAM_SUCCESS)
+                return r;
+
+        (void) sym_pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
+
+        r = bus_call_method(bus, bus_login_mgr, "Inhibit", &error, &reply, "ssss",
+                            inhibit_what,
+                            service ?: "pam_systemd",
+                            inhibit_why ?: "Active PAM session",
+                            "block");
+        if (r < 0) {
+                sym_pam_syslog(pamh, LOG_WARNING, "Failed to acquire inhibit lock: %s", bus_error_message(&error, r));
+                return PAM_SESSION_ERR;
+        }
+
+        int raw_fd;
+        r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_UNIX_FD, &raw_fd);
+        if (r < 0)
+                return pam_bus_log_parse_error(pamh, r);
+
+        _cleanup_close_ int fd = fcntl(raw_fd, F_DUPFD_CLOEXEC, 3);
+        if (fd < 0)
+                return pam_syslog_errno(pamh, LOG_ERR, errno, "Failed to dup inhibit lock fd: %m");
+
+        r = sym_pam_set_data(pamh, "systemd.inhibit-fd", FD_TO_PTR(fd), pam_cleanup_close);
+        if (r != PAM_SUCCESS)
+                return pam_syslog_pam_error(pamh, LOG_ERR, r,
+                                            "Failed to store inhibit lock fd: @PAMERR@");
+        TAKE_FD(fd);
+
+        pam_debug_syslog(pamh, debug, "Acquired inhibit lock for '%s'.", inhibit_what);
+        return PAM_SUCCESS;
+}
+
 _public_ PAM_EXTERN int pam_sm_open_session(
                 pam_handle_t *pamh,
                 int flags,
@@ -1803,6 +1854,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
         c.desktop = getenv_harder(pamh, "XDG_SESSION_DESKTOP", desktop_pam);
         c.area = getenv_harder(pamh, "XDG_AREA", area_pam);
         c.incomplete = getenv_harder_bool(pamh, "XDG_SESSION_INCOMPLETE", false);
+        inhibit_what = getenv_harder(pamh, "XDG_SESSION_INHIBIT", inhibit_what);
+        inhibit_why = getenv_harder(pamh, "XDG_SESSION_INHIBIT_WHY", inhibit_why);
 
         const char *extra_device_access = getenv_harder(pamh, "XDG_SESSION_EXTRA_DEVICE_ACCESS", NULL);
         if (extra_device_access) {
@@ -1828,6 +1881,11 @@ _public_ PAM_EXTERN int pam_sm_open_session(
         if (r != PAM_SUCCESS)
                 return r;
 
+        if (inhibit_what)
+                /* Failure to acquire the inhibit lock is not fatal, session setup should proceed
+                 * regardless. */
+                (void) acquire_inhibit_lock(pamh, inhibit_what, inhibit_why, debug);
+
         r = import_shell_credentials(pamh, debug);
         if (r != PAM_SUCCESS)
                 return r;
@@ -1880,6 +1938,8 @@ _public_ PAM_EXTERN int pam_sm_close_session(
 
         (void) close_osc_context(pamh, debug);
 
+        (void) sym_pam_set_data(pamh, "systemd.inhibit-fd", NULL, NULL);
+
         id = sym_pam_getenv(pamh, "XDG_SESSION_ID");
         if (id) {
                 _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;

From 87fa1c28a5f084b5837e58baf5d6a7482c02f382 Mon Sep 17 00:00:00 2001
From: Clayton Craft <clayton@craftyguy.net>
Date: Sat, 23 May 2026 19:53:56 -0700
Subject: [PATCH 3/3] pam_systemd: document inhibit= and inhibit-why= options

---
 man/pam_systemd.xml | 47 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/man/pam_systemd.xml b/man/pam_systemd.xml
index 5482ced067745..bb464556954f0 100644
--- a/man/pam_systemd.xml
+++ b/man/pam_systemd.xml
@@ -238,6 +238,33 @@
         <xi:include href="version-info.xml" xpointer="v258"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>inhibit=</varname></term>
+
+        <listitem><para>Takes a colon-separated list of inhibitor lock types to acquire for the duration of
+        the session. The <varname>XDG_SESSION_INHIBIT</varname> environment variable (see below) takes
+        precedence. Inhibitor locks prevent the system from performing certain actions, such as sleeping.
+        The supported lock types are the same as for the
+        <citerefentry><refentrytitle>systemd-inhibit</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        command, e.g. <constant>shutdown</constant>, <constant>sleep</constant>, <constant>idle</constant>.
+        If not set, no inhibitor lock is acquired. The lock is taken in block mode, the requested
+        actions are fully inhibited as long as the session is active.</para>
+
+        <xi:include href="version-info.xml" xpointer="v262"/></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>inhibit-why=</varname></term>
+
+        <listitem><para>Takes a short human-readable string describing the reason for the inhibitor lock
+        acquired via <varname>inhibit=</varname>. The <varname>XDG_SESSION_INHIBIT_WHY</varname>
+        environment variable (see below) takes precedence. Defaults to <literal>Active PAM session</literal>
+        if not set. Only has an effect if <varname>inhibit=</varname> or
+        <varname>XDG_SESSION_INHIBIT</varname> is set.</para>
+
+        <xi:include href="version-info.xml" xpointer="v262"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>default-capability-bounding-set=</varname></term>
         <term><varname>default-capability-ambient-set=</varname></term>
@@ -414,6 +441,25 @@
 
         <xi:include href="version-info.xml" xpointer="v260"/></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>$XDG_SESSION_INHIBIT</varname></term>
+
+        <listitem><para>The inhibitor lock types to acquire for the duration of the session, as a
+        colon-separated list. This may be used instead of <varname>inhibit=</varname> on the module
+        parameter line.</para>
+
+        <xi:include href="version-info.xml" xpointer="v262"/></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>$XDG_SESSION_INHIBIT_WHY</varname></term>
+
+        <listitem><para>The reason string for the inhibitor lock. This may be used instead of
+        <varname>inhibit-why=</varname> on the module parameter line.</para>
+
+        <xi:include href="version-info.xml" xpointer="v262"/></listitem>
+      </varlistentry>
     </variablelist>
 
     <para>If not set, <command>pam_systemd</command> will initialize
@@ -528,6 +574,7 @@ session   required   pam_unix.so</programlisting>
       <member><citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
       <member><citerefentry><refentrytitle>loginctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
       <member><citerefentry><refentrytitle>pam_systemd_home</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>systemd-inhibit</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
       <member><citerefentry project='man-pages'><refentrytitle>pam.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
       <member><citerefentry project='man-pages'><refentrytitle>pam.d</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
       <member><citerefentry project='man-pages'><refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
