]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homed: Add InhibitSuspend() method
authorAdrian Vovk <adrianvovk@gmail.com>
Sun, 21 Jan 2024 01:29:40 +0000 (20:29 -0500)
committerLennart Poettering <lennart@poettering.net>
Wed, 31 Jan 2024 08:48:23 +0000 (09:48 +0100)
This returns an FD that can be used to temporarily inhibit the automatic
locking on system suspend behavior of homed. As long as the FD is open,
LockAllHomes() won't lock that home directory on suspend. This allows
desktop environments to implement custom more complicated behavior

man/org.freedesktop.home1.xml
src/home/homed-home-bus.c
src/home/homed-home-bus.h
src/home/homed-home.c
src/home/homed-home.h
src/home/homed-manager-bus.c
src/home/org.freedesktop.home1.conf
src/home/org.freedesktop.home1.policy

index 014f60ed448047cc945c9d2a405c78902ce12244..7c604d2e0c1d0c22810b0ec3d28a6e46d354fbe9 100644 (file)
@@ -101,6 +101,8 @@ node /org/freedesktop/home1 {
               out h send_fd);
       @org.freedesktop.systemd1.Privileged("true")
       ReleaseHome(in  s user_name);
+      InhibitSuspendHome(in  s user_name,
+                         out h send_fd);
       @org.freedesktop.systemd1.Privileged("true")
       LockAllHomes();
       @org.freedesktop.systemd1.Privileged("true")
@@ -166,6 +168,8 @@ node /org/freedesktop/home1 {
 
     <variablelist class="dbus-method" generated="True" extra-ref="ReleaseHome()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="InhibitSuspendHome()"/>
+
     <variablelist class="dbus-method" generated="True" extra-ref="LockAllHomes()"/>
 
     <variablelist class="dbus-method" generated="True" extra-ref="DeactivateAllHomes()"/>
@@ -315,7 +319,7 @@ node /org/freedesktop/home1 {
       <classname>org.freedesktop.home1.Home</classname> interface.</para>
 
       <para><function>LockHome()</function> temporarily suspends access to a home directory, flushing out any
-      cryptographic keys from memory. This is only supported on some back-ends, and usually done during system
+      cryptographic keys from memory. This is only supported on some back-ends, and is usually done during system
       suspend, in order to effectively secure home directories while the system is sleeping. Takes a user
       name as single argument. If an application attempts to access a home directory while it is locked it
       will typically freeze until the home directory is unlocked again. This method is equivalent to
@@ -338,9 +342,10 @@ node /org/freedesktop/home1 {
       re-authenticate when the system comes back from suspending. It should be set by all clients that
       implement a secure lock screen running outside of the user's context, that is brought up when the
       system comes back from suspend and can be used to re-acquire the credentials to unlock the user's home
-      directory. If a home directory has at least one client with an open reference to the home directory
-      that does not support this it is not suspended automatically at system suspend, otherwise it is. This
-      method is equivalent to <function>Acquire()</function> on the
+      directory. A home directory is locked automatically at system suspend only if all clients with open
+      references to the home directory specify that they support this functionality, and no client has
+      temporarily inhibited it (see <function>InhibitSuspendHome()</function> below); otherwise the directory
+      remains unlocked. This method is equivalent to <function>Acquire()</function> on the
       <classname>org.freedesktop.home1.Home</classname> interface.</para>
 
       <para><function>RefHome()</function> is similar to <function>AcquireHome()</function> but takes no user
@@ -358,9 +363,25 @@ node /org/freedesktop/home1 {
       triggered deactivation is completed. This method is equivalent to <function>Release()</function> on the
       <classname>org.freedesktop.home1.Home</classname> interface.</para>
 
+      <para><function>InhibitSuspendHome()</function> temporarily inhibits automatic locking during system
+      suspend for a home directory. It returns a file descriptor that inhibits this functionality for as long
+      as it is open. As mentioned above, locking a home directory requires a secure lock screen running
+      outside of the user context, and is likely to freeze any process that attempts to access the directory.
+      Thus, locking a home directory is a trade-off: it increases security, but prevents the client from
+      displaying any user content on its secure lock screen, including notifications, media controls, contact
+      information for incoming phone calls, and much more. A client may use this method to implement more
+      complicated automatic locking behavior for home directories, in order to solve some of these UX issues.
+      For instance, the client may choose to only lock the home directory and switch to the secure lock screen
+      if the device has been suspended for over 24 hours. Note that this inhibitor does not prevent clients from
+      calling <function>LockHome()</function>, and in fact clients will need to call <function>LockHome()</function>
+      manually as part of their custom behavior to lock the home directory. Clients should take care to ensure that
+      the file descriptor is closed in the event that their custom behavior fails or is disabled. This method is
+      equivalent to <function>InhibitSuspend()</function> on the <classname>org.freedesktop.home1.Home</classname>
+      interface.</para>
+
       <para><function>LockAllHomes()</function> locks all active home directories that only have references
-      that opted into automatic suspending during system suspend. This is usually invoked automatically
-      shortly before system suspend.</para>
+      that opted into automatic locking during system suspend and have no clients inhibiting this behavior.
+      This is usually invoked automatically shortly before system suspend.</para>
 
       <para><function>DeactivateAllHomes()</function> deactivates all home areas that are currently
       active. This is usually invoked automatically shortly before system shutdown.</para>
@@ -416,6 +437,7 @@ node /org/freedesktop/home1/home {
           out h send_fd);
       @org.freedesktop.systemd1.Privileged("true")
       Release();
+      InhibitSuspend(out h send_fd);
     properties:
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly s UserName = '...';
@@ -473,6 +495,8 @@ node /org/freedesktop/home1/home {
 
     <variablelist class="dbus-method" generated="True" extra-ref="Release()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="InhibitSuspend()"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="UserName"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="UID"/>
@@ -493,7 +517,7 @@ node /org/freedesktop/home1/home {
       <function>Fixate()</function>, <function>Authenticate()</function>, <function>Update()</function>,
       <function>Resize()</function>, <function>ChangePassword()</function>, <function>Lock()</function>,
       <function>Unlock()</function>, <function>Acquire()</function>, <function>Ref()</function>,
-      <function>Release()</function> operate like their matching counterparts on the
+      <function>Release()</function>, <function>InhibitSuspend()</function> operate like their matching counterparts on the
       <classname>org.freedesktop.home1.Manager</classname> interface (see above). The main difference is that
       they are methods of the home directory objects, and hence carry no additional user name
       parameter. Which of the two flavors of methods to call depends on the handles to the user known on the
@@ -521,6 +545,18 @@ node /org/freedesktop/home1/home {
 
   <xi:include href="org.freedesktop.locale1.xml" xpointer="versioning"/>
 
+  <refsect1>
+    <title>History</title>
+    <refsect2>
+      <title>The Manager Object</title>
+      <para><function>InhibitSuspendHome()</function> was added in version 256.</para>
+    </refsect2>
+    <refsect2>
+      <title>Home Objects</title>
+      <para><function>InhibitSuspend()</function> was added in version 256.</para>
+    </refsect2>
+  </refsect1>
+
   <refsect1>
     <title>See Also</title>
     <para><simplelist type="inline">
index 413a706f4c363367b586fc2c7c22c422760f23ca..30f5735443ac6214b3a10bbb70600c3e712f103a 100644 (file)
@@ -595,7 +595,7 @@ int bus_home_method_acquire(
                 return r;
 
         /* This operation might not be something we can executed immediately, hence queue it */
-        fd = home_create_fifo(h, please_suspend);
+        fd = home_create_fifo(h, please_suspend ? HOME_FIFO_PLEASE_SUSPEND : HOME_FIFO_DONT_SUSPEND);
         if (fd < 0)
                 return sd_bus_reply_method_errnof(message, fd, "Failed to allocate FIFO for %s: %m", h->user_name);
 
@@ -646,7 +646,7 @@ int bus_home_method_ref(
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
         }
 
-        fd = home_create_fifo(h, please_suspend);
+        fd = home_create_fifo(h, please_suspend ? HOME_FIFO_PLEASE_SUSPEND : HOME_FIFO_DONT_SUSPEND);
         if (fd < 0)
                 return sd_bus_reply_method_errnof(message, fd, "Failed to allocate FIFO for %s: %m", h->user_name);
 
@@ -675,6 +675,53 @@ int bus_home_method_release(
         return 1;
 }
 
+int bus_home_method_inhibit_suspend(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_close_ int fd = -EBADF;
+        Home *h = ASSERT_PTR(userdata);
+        HomeState state;
+        int r;
+
+        r = bus_verify_polkit_async_full(
+                        message,
+                        "org.freedesktop.home1.inhibit-suspend",
+                        /* details= */ NULL,
+                        /* interactive= */ false,
+                        h->uid,
+                        &h->manager->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
+        state = home_get_state(h);
+        switch (state) {
+        case HOME_ABSENT:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
+        case HOME_UNFIXATED:
+        case HOME_INACTIVE:
+        case HOME_DIRTY:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s not active.", h->user_name);
+        case HOME_LOCKED:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
+        default:
+                if (HOME_STATE_IS_ACTIVE(state))
+                        break;
+
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
+        }
+
+        fd = home_create_fifo(h, HOME_FIFO_INHIBIT_SUSPEND);
+        if (fd < 0)
+                return sd_bus_reply_method_errnof(message, fd, "Failed to allocate FIFO for %s: %m", h->user_name);
+
+        return sd_bus_reply_method_return(message, "h", fd);
+}
+
 /* We map a uid_t as uint32_t bus property, let's ensure this is safe. */
 assert_cc(sizeof(uid_t) == sizeof(uint32_t));
 
@@ -819,6 +866,11 @@ const sd_bus_vtable home_vtable[] = {
                                 bus_home_method_ref,
                                 0),
         SD_BUS_METHOD("Release", NULL, NULL, bus_home_method_release, 0),
+        SD_BUS_METHOD_WITH_ARGS("InhibitSuspend",
+                                SD_BUS_NO_ARGS,
+                                SD_BUS_RESULT("h", send_fd),
+                                bus_home_method_inhibit_suspend,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_VTABLE_END
 };
 
index 552217805560cb47bda8b193af37993379f1e509..a791c763126203f300232474f7aa223c52d11ebc 100644 (file)
@@ -25,6 +25,7 @@ int bus_home_method_unlock(sd_bus_message *message, void *userdata, sd_bus_error
 int bus_home_method_acquire(sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_home_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_home_method_release(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_home_method_inhibit_suspend(sd_bus_message *message, void *userdata, sd_bus_error *error);
 
 extern const BusObjectImplementation home_object;
 
index 00fc7f114aceb2fc8652e0e13d582564afed03fe..fc9dfcff24569c6f4f78effea6e34ab94d030846 100644 (file)
@@ -211,6 +211,7 @@ Home *home_free(Home *h) {
 
         h->ref_event_source_please_suspend = sd_event_source_disable_unref(h->ref_event_source_please_suspend);
         h->ref_event_source_dont_suspend = sd_event_source_disable_unref(h->ref_event_source_dont_suspend);
+        h->inhibit_suspend_event_source = sd_event_source_disable_unref(h->inhibit_suspend_event_source);
 
         h->pending_operations = ordered_set_free(h->pending_operations);
         h->pending_event_source = sd_event_source_disable_unref(h->pending_event_source);
@@ -2614,6 +2615,9 @@ static int on_home_ref_eof(sd_event_source *s, int fd, uint32_t revents, void *u
         if (h->ref_event_source_dont_suspend == s)
                 h->ref_event_source_dont_suspend = sd_event_source_disable_unref(h->ref_event_source_dont_suspend);
 
+        if (h->inhibit_suspend_event_source == s)
+                h->inhibit_suspend_event_source = sd_event_source_disable_unref(h->inhibit_suspend_event_source);
+
         if (home_is_referenced(h))
                 return 0;
 
@@ -2629,25 +2633,42 @@ static int on_home_ref_eof(sd_event_source *s, int fd, uint32_t revents, void *u
         return 0;
 }
 
-int home_create_fifo(Home *h, bool please_suspend) {
+int home_create_fifo(Home *h, HomeFifoType type) {
+        static const struct {
+                const char *suffix;
+                const char *description;
+                size_t event_source;
+        } table[_HOME_FIFO_TYPE_MAX] = {
+                [HOME_FIFO_PLEASE_SUSPEND] = {
+                        .suffix = ".please-suspend",
+                        .description = "acquire-ref",
+                        .event_source = offsetof(Home, ref_event_source_please_suspend),
+                },
+                [HOME_FIFO_DONT_SUSPEND] = {
+                        .suffix = ".dont-suspend",
+                        .description = "acquire-ref-dont-suspend",
+                        .event_source = offsetof(Home, ref_event_source_dont_suspend),
+                },
+                [HOME_FIFO_INHIBIT_SUSPEND] = {
+                        .suffix = ".inhibit-suspend",
+                        .description = "inhibit-suspend",
+                        .event_source = offsetof(Home, inhibit_suspend_event_source),
+                },
+        };
+
         _cleanup_close_ int ret_fd = -EBADF;
-        sd_event_source **ss;
-        const char *fn, *suffix;
+        sd_event_source **evt;
+        const char *fn;
         int r;
 
         assert(h);
+        assert(type >= 0 && type < _HOME_FIFO_TYPE_MAX);
 
-        if (please_suspend) {
-                suffix = ".please-suspend";
-                ss = &h->ref_event_source_please_suspend;
-        } else {
-                suffix = ".dont-suspend";
-                ss = &h->ref_event_source_dont_suspend;
-        }
+        evt = (sd_event_source**) ((uint8_t*) h + table[type].event_source);
 
-        fn = strjoina("/run/systemd/home/", h->user_name, suffix);
+        fn = strjoina("/run/systemd/home/", h->user_name, table[type].suffix);
 
-        if (!*ss) {
+        if (!*evt) {
                 _cleanup_close_ int ref_fd = -EBADF;
 
                 (void) mkdir("/run/systemd/home/", 0755);
@@ -2658,20 +2679,19 @@ int home_create_fifo(Home *h, bool please_suspend) {
                 if (ref_fd < 0)
                         return log_error_errno(errno, "Failed to open FIFO %s for reading: %m", fn);
 
-                r = sd_event_add_io(h->manager->event, ss, ref_fd, 0, on_home_ref_eof, h);
+                r = sd_event_add_io(h->manager->event, evt, ref_fd, 0, on_home_ref_eof, h);
                 if (r < 0)
                         return log_error_errno(r, "Failed to allocate reference FIFO event source: %m");
 
-                (void) sd_event_source_set_description(*ss, "acquire-ref");
+                (void) sd_event_source_set_description(*evt, table[type].description);
 
-                r = sd_event_source_set_priority(*ss, SD_EVENT_PRIORITY_IDLE-1);
+                r = sd_event_source_set_priority(*evt, SD_EVENT_PRIORITY_IDLE-1);
                 if (r < 0)
                         return r;
 
-                r = sd_event_source_set_io_fd_own(*ss, true);
+                r = sd_event_source_set_io_fd_own(*evt, true);
                 if (r < 0)
                         return log_error_errno(r, "Failed to pass ownership of FIFO event fd to event source: %m");
-
                 TAKE_FD(ref_fd);
         }
 
@@ -2751,8 +2771,10 @@ bool home_is_referenced(Home *h) {
 bool home_shall_suspend(Home *h) {
         assert(h);
 
-        /* Suspend if there's at least one client referencing this home directory that wants a suspend and none who does not. */
-        return h->ref_event_source_please_suspend && !h->ref_event_source_dont_suspend;
+        /* We lock the home area on suspend if... */
+        return h->ref_event_source_please_suspend && /* at least one client supports suspend, and... */
+               !h->ref_event_source_dont_suspend && /* no clients lack support for suspend, and... */
+               !h->inhibit_suspend_event_source; /* no client is temporarily inhibiting suspend */
 }
 
 static int home_dispatch_release(Home *h, Operation *o) {
index 1226c0c6ba4d6105aea5eb1ce318b90f5d73b7fd..b8b004642294628db7bd2c68ebcfe32a280ca79c 100644 (file)
@@ -138,12 +138,17 @@ struct Home {
         bool unregister_on_failure;
 
         /* The reading side of a FIFO stored in /run/systemd/home/, the writing side being used for reference
-         * counting. The references dropped to zero as soon as we see EOF. This concept exists twice: once
-         * for clients that are fine if we suspend the home directory on system suspend, and once for clients
-         * that are not ok with that. This allows us to determine for each home whether there are any clients
-         * that support unsuspend. */
+         * counting. The references dropped to zero as soon as we see EOF. This concept exists thrice: once
+         * for clients that are fine if we lock the home directory on system suspend, once for clients
+         * that are not ok with that, and once for clients that are usually ok with it but temporarily
+         * want to opt-out so that they can implement more advanced behavior on their own. This allows
+         * us to determine for each home whether there are any clients that don't support suspend at this
+         * moment. */
         sd_event_source *ref_event_source_please_suspend;
         sd_event_source *ref_event_source_dont_suspend;
+        /* This is distinct from ref_event_source_dont_suspend because it can be obtained from unprivileged
+         * code, and thus we don't count it as a reference on the home area. */
+        sd_event_source *inhibit_suspend_event_source;
 
         /* Any pending operations we still need to execute. These are for operations we want to queue if we
          * can't execute them right-away. */
@@ -209,7 +214,15 @@ int home_killall(Home *h);
 
 int home_augment_status(Home *h, UserRecordLoadFlags flags, UserRecord **ret);
 
-int home_create_fifo(Home *h, bool please_suspend);
+typedef enum {
+        HOME_FIFO_PLEASE_SUSPEND,
+        HOME_FIFO_DONT_SUSPEND,
+        HOME_FIFO_INHIBIT_SUSPEND,
+        _HOME_FIFO_TYPE_MAX,
+        _HOME_FIFO_TYPE_INVALID = -EINVAL,
+} HomeFifoType;
+int home_create_fifo(Home *h, HomeFifoType mode);
+
 int home_schedule_operation(Home *h, Operation *o, sd_bus_error *error);
 
 int home_auto_login(Home *h, char ***ret_seats);
index d45e0f123855ae543c8922b1f0d785e2a810b195..c613eed4d5634218283815f25f7a4314e341e7e3 100644 (file)
@@ -591,6 +591,10 @@ static int method_release_home(sd_bus_message *message, void *userdata, sd_bus_e
         return generic_home_method(userdata, message, bus_home_method_release, error);
 }
 
+static int method_inhibit_suspend_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_inhibit_suspend, error);
+}
+
 static int method_lock_all_homes(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         _cleanup_(operation_unrefp) Operation *o = NULL;
         bool waiting = false;
@@ -845,6 +849,12 @@ static const sd_bus_vtable manager_vtable[] = {
                                 method_release_home,
                                 0),
 
+        SD_BUS_METHOD_WITH_ARGS("InhibitSuspendHome",
+                                SD_BUS_ARGS("s", user_name),
+                                SD_BUS_RESULT("h", send_fd),
+                                method_inhibit_suspend_home,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+
         /* An operation that acts on all homes that allow it */
         SD_BUS_METHOD("LockAllHomes", NULL, NULL, method_lock_all_homes, 0),
         SD_BUS_METHOD("DeactivateAllHomes", NULL, NULL, method_deactivate_all_homes, 0),
index 5af1a686074433915c84f416df1ed2647e5af08d..6d13535f957ebcf41c596f78de0cff9b7b1ff969 100644 (file)
                        send_interface="org.freedesktop.home1.Manager"
                        send_member="ReleaseHome"/>
 
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="InhibitSuspendHome"/>
+
                 <allow send_destination="org.freedesktop.home1"
                        send_interface="org.freedesktop.home1.Manager"
                        send_member="LockAllHomes"/>
                        send_interface="org.freedesktop.home1.Home"
                        send_member="Release"/>
 
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="InhibitSuspend"/>
+
                 <allow receive_sender="org.freedesktop.home1"/>
         </policy>
 
index a337b32237b563e6ccd8840cf8ddc991e62bd165..2ac710d66c6101ab0a11b24134a1ee75079e3ed5 100644 (file)
                 </defaults>
         </action>
 
+        <action id="org.freedesktop.home1.inhibit-suspend">
+                <description gettext-domain="systemd">Inhibit automatic lock of a home area</description>
+                <message gettext-domain="systemd">Authentication is required to inhibit automatic lock of a user's home area.</message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
 </policyconfig>