Merge pull request #30840 from AdrianVovk/homed-bulk-v2
Homed bulk directories v2
This commit is contained in:
@@ -2245,10 +2245,6 @@ Features:
|
||||
can easily set overall quota for all users
|
||||
- on login, if we can't fallocate initially, but rebalance is on, then allow
|
||||
login in discard mode, then immediately rebalance, then turn off discard
|
||||
- extend user records with optional "bulk" data. Specifically, a user
|
||||
avatar/photo or so. This data should be stored along with the user record,
|
||||
but probably shouldn't be part of the record itself, since it might be
|
||||
large.
|
||||
- add "homectl unbind" command to remove local user record of an inactive
|
||||
home dir
|
||||
|
||||
|
||||
+23
-4
@@ -74,6 +74,10 @@ the following extensions are envisioned:
|
||||
Similar to JSON User Records there are also
|
||||
[JSON Group Records](GROUP_RECORD.md) that encapsulate UNIX groups.
|
||||
|
||||
JSON User Records are not suitable for storing all identity information about
|
||||
the user, such as binary data or large unstructured blobs of text. These parts
|
||||
of a user's identity should be stored in the [Blob Directories](USER_RECORD_BLOB_DIRS.md).
|
||||
|
||||
JSON User Records may be transferred or written to disk in various protocols
|
||||
and formats. To inquire about such records defined on the local system use the
|
||||
[User/Group Lookup API via Varlink](USER_GROUP_API.md). User/group records may
|
||||
@@ -230,6 +234,16 @@ optional, when unset the user should not be considered part of any realm. A
|
||||
user record with a realm set is never compatible (for the purpose of updates,
|
||||
see above) with a user record without one set, even if the `userName` field matches.
|
||||
|
||||
`blobDirectory` → The absolute path to a world-readable copy of the user's blob
|
||||
directory. See [Blob Directories](USER_RECORD_BLOB_DIRS.md) for more details.
|
||||
|
||||
`blobManifest` → An object, which maps valid blob directory filenames (see
|
||||
[Blob Directories](USER_RECORD_BLOB_DIRS.md) for requirements) to SHA256 hashes
|
||||
formatted as hex strings. This exists for the purpose of including the contents
|
||||
of the blob directory in the record's signature. Managers that support blob
|
||||
directories and utilize signed user records (like `systemd-homed`) should use
|
||||
this field to verify the contents of the blob directory whenever appropriate.
|
||||
|
||||
`realName` → The real name of the user, a string. This should contain the
|
||||
user's real ("human") name, and corresponds loosely to the GECOS field of
|
||||
classic UNIX user records. When converting a `struct passwd` to a JSON user
|
||||
@@ -754,7 +768,7 @@ These two are the only two fields specific to this section. All other fields
|
||||
that may be used in this section are identical to the equally named ones in the
|
||||
`regular` section (i.e. at the top-level object). Specifically, these are:
|
||||
|
||||
`iconName`, `location`, `shell`, `umask`, `environment`, `timeZone`,
|
||||
`blobDirectory`, `blobManifest`, `iconName`, `location`, `shell`, `umask`, `environment`, `timeZone`,
|
||||
`preferredLanguage`, `additionalLanguages`, `niceLevel`, `resourceLimits`, `locked`, `notBeforeUSec`,
|
||||
`notAfterUSec`, `storage`, `diskSize`, `diskSizeRelative`, `skeletonDirectory`,
|
||||
`accessMode`, `tasksMax`, `memoryHigh`, `memoryMax`, `cpuWeight`, `ioWeight`,
|
||||
@@ -806,9 +820,9 @@ The following fields are defined in the `binding` section. They all have an
|
||||
identical format and override their equally named counterparts in the `regular`
|
||||
and `perMachine` sections:
|
||||
|
||||
`imagePath`, `homeDirectory`, `partitionUuid`, `luksUuid`, `fileSystemUuid`,
|
||||
`uid`, `gid`, `storage`, `fileSystemType`, `luksCipher`, `luksCipherMode`,
|
||||
`luksVolumeKeySize`.
|
||||
`blobDirectory`, `imagePath`, `homeDirectory`, `partitionUuid`, `luksUuid`,
|
||||
`fileSystemUuid`, `uid`, `gid`, `storage`, `fileSystemType`, `luksCipher`,
|
||||
`luksCipherMode`, `luksVolumeKeySize`.
|
||||
|
||||
## Fields in the `status` section
|
||||
|
||||
@@ -1098,6 +1112,7 @@ A fully featured user record associated with a home directory managed by
|
||||
"fileSystemUuid" : "758e88c8-5851-4a2a-b88f-e7474279c111",
|
||||
"gid" : 60232,
|
||||
"homeDirectory" : "/home/grobie",
|
||||
"blobDirectory" : "/var/cache/systemd/homed/grobie/",
|
||||
"imagePath" : "/home/grobie.home",
|
||||
"luksCipher" : "aes",
|
||||
"luksCipherMode" : "xts-plain64",
|
||||
@@ -1108,6 +1123,10 @@ A fully featured user record associated with a home directory managed by
|
||||
"uid" : 60232
|
||||
}
|
||||
},
|
||||
"blobManifest" : {
|
||||
"avatar" : "c0636851d25a62d817ff7da4e081d1e646e42c74d0ecb53425f75fcf1ba43b52",
|
||||
"login-background" : "da7ad0222a6edbc6cd095149c72d38d92fd3114f606e4b57469857ef47fade18"
|
||||
},
|
||||
"disposition" : "regular",
|
||||
"enforcePasswordPolicy" : false,
|
||||
"lastChangeUSec" : 1565950024279735,
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
title: User Record Blob Directories
|
||||
category: Users, Groups and Home Directories
|
||||
layout: default
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
---
|
||||
|
||||
# User Record Blob Directories
|
||||
|
||||
The blob directories are for storing binary or unstructured data that would
|
||||
otherwise be stored in [JSON User Records](USER_RECORD.md). For instance,
|
||||
this includes image files such as the user's avatar picture. This data,
|
||||
like most of the user record, will be made publicly available to the
|
||||
system.
|
||||
|
||||
The JSON User Record specifies the location of the blob directory via the
|
||||
`blobDirectory` field. If the field is unset, then there is no blob directory
|
||||
and thus no blob files to look for. Note that `blobDirectory` can exist in the
|
||||
`regular`, `perMachine`, and `status` sections. The blob directory is completely
|
||||
owned and managed by the service that owns the rest of the user record (as
|
||||
specified in the `service` field).
|
||||
|
||||
For consistency, blob directories have certain restrictions placed on them
|
||||
that may be enforced by their owning service. Services implementing blob
|
||||
directories are free to ignore these restrictions, but software that wishes
|
||||
to store some of its data in blob directories must adhere to the following:
|
||||
|
||||
* The directory only contains regular files; no sub-directories or any special
|
||||
files are permitted.
|
||||
|
||||
* Filenames inside of the directory are restricted to
|
||||
[URI Unreserved Characters](https://www.rfc-editor.org/rfc/rfc3986#section-2.3)
|
||||
(alphanumeric, `-`, `.`, `_`, and `~`), and must not start with a dot.
|
||||
|
||||
* The total size of the directory should not exceed 64M.
|
||||
|
||||
* File ownership and permissions will not be preserved. The service may reset
|
||||
the mode of the files to 0644, and ownership to whatever it wishes.
|
||||
|
||||
* Timestamps, xattrs, ACLs, or any other metadata on the files will not be preserved.
|
||||
|
||||
Services are required to ensure that the directory and its contents are
|
||||
world-readable. Aside from this requirement, services are free to provide
|
||||
the directory and its contents in whatever manner they like, including but
|
||||
not limited to synthesizing the directory at runtime using external data
|
||||
or keeping around multiple copies. Thus, only the service that owns the
|
||||
directory is permitted to write to this directory in any way: for all
|
||||
other software the directory is strictly read-only.
|
||||
|
||||
Services may choose to provide some way to change user records. Services
|
||||
that provide this functionality should support changing the blob directory also.
|
||||
Care must be taken to avoid exposing sensitive data to malicious clients. This
|
||||
includes but is not limited to disallowing symlinks and using file descriptors
|
||||
(excluding O_PATH!) to ensure that the client actually has permission to access
|
||||
the data it wants the service to publish.
|
||||
|
||||
Services that make use of the `signature` section in the records they manage
|
||||
should enforce `blobManifest`. This ensures that the contents of the blob directory
|
||||
are part of the cryptographically signed data.
|
||||
|
||||
## Known Files
|
||||
|
||||
Various files in the blob directories have known semantic meanings.
|
||||
The following files are currently defined:
|
||||
|
||||
`avatar` → An image file that should be used as the user's avatar picture.
|
||||
The exact file type and resolution of this image are left unspecified,
|
||||
and requirements will depend on the capabilities of the components that will
|
||||
display it. However, we suggest the use of commonly-supported picture formats
|
||||
(i.e. PNG or JPEG) and a resolution of 512 x 512. This image should not have any
|
||||
transparency. If missing, of an incompatible file type, or otherwise unusable,
|
||||
then the user does not have a profile picture and a default will be used instead.
|
||||
|
||||
`login-background` → An image file that will be used as the user's background on the
|
||||
login screen (i.e. in GDM). The exact file type and resolution are left unspecified
|
||||
and are ultimately up to the components that will render this background image. This
|
||||
image should not have any transparency. If missing, of an incompatible file type, or
|
||||
otherwise unusable, a fallback background of some kind will be used.
|
||||
|
||||
## Extending These Directories
|
||||
|
||||
Like JSON User Records, the blob directories are intended to be extendable for
|
||||
various applications. In general, subsystems are free to introduce their own
|
||||
files, as long as:
|
||||
|
||||
* The requirements listed above are all met.
|
||||
|
||||
* Care is taken to avoid namespace clashes. Please prefix your file names with
|
||||
a short identifier of your project to avoid ambiguities and incompatibilities.
|
||||
|
||||
* This specification is supposed to be a living specification. If you need
|
||||
additional files, please consider defining them upstream for inclusion in
|
||||
this specification. If they are reasonably universally useful, it would be
|
||||
best to list them here.
|
||||
|
||||
## Examples
|
||||
|
||||
The simplest way to define a user record is via the drop-in directories (as documented
|
||||
in [nss-systemd(8)](https://www.freedesktop.org/software/systemd/man/latest/nss-systemd.html)
|
||||
and [systemd-userdb.service(8)](https://www.freedesktop.org/software/systemd/man/latest/systemd-userdbd.service.html)).
|
||||
Such records can have blob directories by simply referring to some persistent
|
||||
place from the record, possibly next to the record itself. For instance,
|
||||
`/etc/userdb/grobie.user` may contain:
|
||||
|
||||
```json
|
||||
{
|
||||
"userName": "grobie",
|
||||
"disposition": "regular",
|
||||
"homeDirectory": "/home/grobie",
|
||||
"blobDirectory": "/etc/userdb/grobie.blob/",
|
||||
}
|
||||
```
|
||||
|
||||
In this case, `/etc/userdb/grobie.blob/` will be the blob directory for the
|
||||
user `grobie`.
|
||||
|
||||
A more complicated case is a home directory managed by `systemd-homed.service`.
|
||||
When it manages a home directory, it maintains and synchronizes two separate
|
||||
blob directories: one belonging to the system in `/var/cache/systemd/home`,
|
||||
and another belonging to the home directory in `~/.identity-blob`. The system
|
||||
blob directory ensures that the blob data is available while the home directory
|
||||
is encrypted or otherwise unavailable, and the home blob directory ensures that
|
||||
the user account remains portable between systems. To implement this behavior,
|
||||
`systemd-homed.service` always sets `blobDirectory` to the system blob directory
|
||||
in the `binding` section of the user record (i.e. this is _not_ persisted to
|
||||
`~/.identity`). If some client tries to update the user record with a new blob
|
||||
directory, `systemd-homed.service` will copy the updated blob directory into both
|
||||
the system and home blob locations.
|
||||
@@ -507,6 +507,40 @@
|
||||
<xi:include href="version-info.xml" xpointer="v247"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--blob=</option><replaceable>PATH</replaceable></term>
|
||||
<term><option>-b</option> <replaceable>PATH</replaceable></term>
|
||||
<term><option>--blob=</option><replaceable>FILENAME</replaceable>=<replaceable>PATH</replaceable></term>
|
||||
<term><option>-b</option> <replaceable>FILENAME</replaceable>=<replaceable>PATH</replaceable></term>
|
||||
|
||||
<listitem><para>Accepts either a directory path, or a file name followed by a file path. If just a
|
||||
directory path is specified, then the user's entire blob directory is replaced the specified path.
|
||||
Note that this replacement is performed before per-file manipulations are applied, which means these per-file
|
||||
manipulations will be applied on top of the specified directory. If a filename and file path are specified, then
|
||||
the single specified blob file will be overwritten with the specified path. If completely blank, the entire blob
|
||||
directory is emptied out (which also resets all previous blob-related flags up to this point). If a filename is
|
||||
specified but the corresponding path is blank, that single file will be deleted from the blob directory. All changes
|
||||
are performed in temporary copies of the specified files in directories, which means that the originals specified on
|
||||
the command line are not modified. See <ulink url="https://systemd.io/USER_RECORD_BLOB_DIRS">User Record Blob Directories</ulink>
|
||||
for more information about blob directories.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--avatar=</option><replaceable>PATH</replaceable></term>
|
||||
<term><option>--login-background=</option><replaceable>PATH</replaceable></term>
|
||||
|
||||
<listitem><para>Accept a file path. If set, the specifified file is used to overwrite the
|
||||
corresponding file in the user's blob directory. If blank, the corresponding file is deleted
|
||||
from the blob directory. Essentially, these options are shortcuts to
|
||||
<option>--blob=</option><replaceable>FILENAME</replaceable>=<replaceable>PATH</replaceable>
|
||||
for the known filenames defined in
|
||||
<ulink url="https://systemd.io/USER_RECORD_BLOB_DIRS">User Record Blob Directories</ulink>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--locked=</option><replaceable>BOOLEAN</replaceable></term>
|
||||
|
||||
|
||||
@@ -72,6 +72,9 @@ node /org/freedesktop/home1 {
|
||||
RegisterHome(in s user_record);
|
||||
UnregisterHome(in s user_name);
|
||||
CreateHome(in s user_record);
|
||||
CreateHomeEx(in s user_record,
|
||||
in a{sh} blobs,
|
||||
in t flags);
|
||||
RealizeHome(in s user_name,
|
||||
in s secret);
|
||||
RemoveHome(in s user_name);
|
||||
@@ -81,6 +84,9 @@ node /org/freedesktop/home1 {
|
||||
AuthenticateHome(in s user_name,
|
||||
in s secret);
|
||||
UpdateHome(in s user_record);
|
||||
UpdateHomeEx(in s user_record,
|
||||
in a{sh} blobs,
|
||||
in t flags);
|
||||
ResizeHome(in s user_name,
|
||||
in t size,
|
||||
in s secret);
|
||||
@@ -151,6 +157,8 @@ node /org/freedesktop/home1 {
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="CreateHome()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="CreateHomeEx()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="RealizeHome()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="RemoveHome()"/>
|
||||
@@ -161,6 +169,8 @@ node /org/freedesktop/home1 {
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="UpdateHome()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="UpdateHomeEx()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="ResizeHome()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="ChangePasswordHome()"/>
|
||||
@@ -265,6 +275,19 @@ node /org/freedesktop/home1 {
|
||||
the user record locally and creates a home directory matching it, depending on the settings specified
|
||||
in the record in combination with local configuration.</para>
|
||||
|
||||
<para><function>CreateHomeEx()</function> is like <function>CreateHome()</function>, but it allows the
|
||||
home directory to be created with a pre-populated blob directory (see
|
||||
<ulink url="https://systemd.io/USER_RECORD_BLOB_DIRS">User Record Blob Directories</ulink> for more info).
|
||||
This can be done via the dictionary passed as the <varname>blobs</varname> argument to this method: the values
|
||||
are open file descriptors to regular files, and the keys are the filenames that should contain their respective
|
||||
file's data in the blob directory. Note that for security reasons, the file descriptors passed into this method
|
||||
must have enough privileges to read their target file and thus cannot be <literal>O_PATH</literal>; this
|
||||
is done to ensure the caller is actually permitted to read the file they are asking to publish in the
|
||||
blob directories. If the user record passed as the first argument contains a <literal>blobManifest</literal>
|
||||
field it will be enforced; otherwise, a <literal>blobManifest</literal> field will be generated and inserted
|
||||
into the record. The <varname>flags</varname> argument may be used for future expansion, but for now
|
||||
pass 0.</para>
|
||||
|
||||
<para><function>RealizeHome()</function> creates a home directory whose user record is already
|
||||
registered locally. This takes a user name plus a user record consisting only of the
|
||||
<literal>secret</literal> section. Invoking <function>RegisterHome()</function> followed by
|
||||
@@ -308,6 +331,14 @@ node /org/freedesktop/home1 {
|
||||
as the storage resized using <function>ResizeHome()</function>. This method is equivalent to
|
||||
<function>Update()</function> on the <classname>org.freedesktop.home1.Home</classname> interface.</para>
|
||||
|
||||
<para><function>UpdateHomeEx()</function> is like <function>UpdateHome()</function>, but it allows for
|
||||
changes to the blob directory (see <ulink url="https://systemd.io/USER_RECORD_BLOB_DIRS">User Record Blob
|
||||
Directories</ulink> for more info). The <varname>blobs</varname> argument works in the same way as
|
||||
<function>CreateHomeEx()</function>, so check there for details. The new blob directory contents passed into
|
||||
this method will completely replace the user's existing blob directory. The <varname>flags</varname> argument
|
||||
may be used for future expansion, but for now pass 0. This method is equivalent to <function>UpdateEx()</function>
|
||||
on the <classname>org.freedesktop.home1.Home</classname> interface.</para>
|
||||
|
||||
<para><function>ResizeHome()</function> resizes the storage associated with a user record. Takes a user
|
||||
name, a disk size in bytes and a user record consisting only of the <literal>secret</literal> section
|
||||
as argument. If the size is specified as <constant>UINT64_MAX</constant> the storage is resized to the
|
||||
@@ -438,6 +469,9 @@ node /org/freedesktop/home1/home {
|
||||
Fixate(in s secret);
|
||||
Authenticate(in s secret);
|
||||
Update(in s user_record);
|
||||
UpdateEx(in s user_record,
|
||||
in a{sh} blobs,
|
||||
in t flags);
|
||||
Resize(in t size,
|
||||
in s secret);
|
||||
ChangePassword(in s new_secret,
|
||||
@@ -504,6 +538,8 @@ node /org/freedesktop/home1/home {
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="Update()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="UpdateEx()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="Resize()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="ChangePassword()"/>
|
||||
@@ -540,11 +576,11 @@ node /org/freedesktop/home1/home {
|
||||
<para><function>Activate()</function>, <function>ActivateIfReferenced()</function>,
|
||||
<function>Deactivate()</function>, <function>Unregister()</function>, <function>Realize()</function>,
|
||||
<function>Remove()</function>, <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>RefUnrestricted()</function>, <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
|
||||
<function>Update()</function>, <function>UpdateEx()</function>, <function>Resize()</function>,
|
||||
<function>ChangePassword()</function>, <function>Lock()</function>, <function>Unlock()</function>,
|
||||
<function>Acquire()</function>, <function>Ref()</function>, <function>RefUnrestricted()</function>,
|
||||
<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
|
||||
client side: if only the user name is known, it's preferable to use the methods on the manager object
|
||||
@@ -575,11 +611,13 @@ node /org/freedesktop/home1/home {
|
||||
<title>History</title>
|
||||
<refsect2>
|
||||
<title>The Manager Object</title>
|
||||
<para><function>InhibitSuspendHome()</function>, <function>ActivateHomeIfReferenced()</function>, <function>RefHomeUnrestricted()</function> wer added in version 256.</para>
|
||||
<para><function>InhibitSuspendHome()</function>, <function>ActivateHomeIfReferenced()</function>, <function>RefHomeUnrestricted()</function>,
|
||||
<function>CreateHomeEx()</function>, and <function>UpdateHomeEx()</function> were added in version 256.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Home Objects</title>
|
||||
<para><function>InhibitSuspend()</function>, <function>ActivateIfReferenced()</function> and <function>RefUnrestricted()</function> were added in version 256.</para>
|
||||
<para><function>InhibitSuspend()</function>, <function>ActivateIfReferenced()</function>, <function>RefUnrestricted()</function>, and
|
||||
<function>UpdateEx()</function> were added in version 256.</para>
|
||||
</refsect2>
|
||||
</refsect1>
|
||||
|
||||
|
||||
@@ -46,6 +46,10 @@
|
||||
url="https://systemd.io/USER_GROUP_API">User/Group Record Lookup API via Varlink</ulink>, and thus may be
|
||||
browsed with
|
||||
<citerefentry><refentrytitle>userdbctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para><filename>systemd-homed.service</filename> also manages blob directories for each home directory
|
||||
it manages. See <ulink url="https://systemd.io/USER_RECORD_BLOB_DIRS">User Record Blob Directories</ulink>
|
||||
for more details.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
|
||||
@@ -106,7 +106,10 @@ _homectl() {
|
||||
--cifs-service
|
||||
--stop-delay
|
||||
--kill-processes
|
||||
--auto-login'
|
||||
--auto-login
|
||||
-b --blob
|
||||
--avatar
|
||||
--login-background'
|
||||
)
|
||||
|
||||
if __contains_word "$prev" ${OPTS[ARG]}; then
|
||||
@@ -117,7 +120,7 @@ _homectl() {
|
||||
--machine|-M)
|
||||
comps=$( __get_machines )
|
||||
;;
|
||||
--identity|--image-path)
|
||||
--identity|--image-path|--avatar|--login-background)
|
||||
comps=$(compgen -A file -- "$cur" )
|
||||
compopt -o filenames
|
||||
;;
|
||||
@@ -130,7 +133,7 @@ _homectl() {
|
||||
--locked|--enforce-password-policy|--password-change-now|--luks-discard|--luks-offline-discard|--nosuid|--nodev|--noexec|--kill-processes|--auto-login)
|
||||
comps='yes no'
|
||||
;;
|
||||
-d|--home-dir|--skel)
|
||||
-d|--home-dir|--skel|-b|--blob)
|
||||
comps=$(compgen -A directory -- "$cur" )
|
||||
compopt -o dirnames
|
||||
;;
|
||||
|
||||
@@ -464,6 +464,53 @@ int close_all_fds(const int except[], size_t n_except) {
|
||||
return r;
|
||||
}
|
||||
|
||||
int pack_fds(int fds[], size_t n_fds) {
|
||||
if (n_fds <= 0)
|
||||
return 0;
|
||||
|
||||
/* Shifts around the fds in the provided array such that they
|
||||
* all end up packed next to each-other, in order, starting
|
||||
* from SD_LISTEN_FDS_START. This must be called after close_all_fds();
|
||||
* it is likely to freeze up otherwise. You should probably use safe_fork_full
|
||||
* with FORK_CLOSE_ALL_FDS|FORK_PACK_FDS set, to ensure that this is done correctly.
|
||||
* The fds array is modified in place with the new FD numbers. */
|
||||
|
||||
assert(fds);
|
||||
|
||||
for (int start = 0;;) {
|
||||
int restart_from = -1;
|
||||
|
||||
for (int i = start; i < (int) n_fds; i++) {
|
||||
int nfd;
|
||||
|
||||
/* Already at right index? */
|
||||
if (fds[i] == i + 3)
|
||||
continue;
|
||||
|
||||
nfd = fcntl(fds[i], F_DUPFD, i + 3);
|
||||
if (nfd < 0)
|
||||
return -errno;
|
||||
|
||||
safe_close(fds[i]);
|
||||
fds[i] = nfd;
|
||||
|
||||
/* Hmm, the fd we wanted isn't free? Then
|
||||
* let's remember that and try again from here */
|
||||
if (nfd != i + 3 && restart_from < 0)
|
||||
restart_from = i;
|
||||
}
|
||||
|
||||
if (restart_from < 0)
|
||||
break;
|
||||
|
||||
start = restart_from;
|
||||
}
|
||||
|
||||
assert(fds[0] == 3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int same_fd(int a, int b) {
|
||||
struct stat sta, stb;
|
||||
pid_t pid;
|
||||
|
||||
@@ -77,6 +77,8 @@ int get_max_fd(void);
|
||||
int close_all_fds(const int except[], size_t n_except);
|
||||
int close_all_fds_without_malloc(const int except[], size_t n_except);
|
||||
|
||||
int pack_fds(int fds[], size_t n);
|
||||
|
||||
int same_fd(int a, int b);
|
||||
|
||||
void cmsg_close_all(struct msghdr *mh);
|
||||
|
||||
@@ -1468,7 +1468,7 @@ static int fork_flags_to_signal(ForkFlags flags) {
|
||||
int safe_fork_full(
|
||||
const char *name,
|
||||
const int stdio_fds[3],
|
||||
const int except_fds[],
|
||||
int except_fds[],
|
||||
size_t n_except_fds,
|
||||
ForkFlags flags,
|
||||
pid_t *ret_pid) {
|
||||
@@ -1697,6 +1697,19 @@ int safe_fork_full(
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & FORK_PACK_FDS) {
|
||||
/* FORK_CLOSE_ALL_FDS ensures that except_fds are the only FDs >= 3 that are
|
||||
* open, this is including the log. This is required by pack_fds, which will
|
||||
* get stuck in an infinite loop of any FDs other than except_fds are open. */
|
||||
assert(FLAGS_SET(flags, FORK_CLOSE_ALL_FDS));
|
||||
|
||||
r = pack_fds(except_fds, n_except_fds);
|
||||
if (r < 0) {
|
||||
log_full_errno(prio, r, "Failed to pack file descriptors: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & FORK_CLOEXEC_OFF) {
|
||||
r = fd_cloexec_many(except_fds, n_except_fds, false);
|
||||
if (r < 0) {
|
||||
@@ -1736,7 +1749,7 @@ int safe_fork_full(
|
||||
int pidref_safe_fork_full(
|
||||
const char *name,
|
||||
const int stdio_fds[3],
|
||||
const int except_fds[],
|
||||
int except_fds[],
|
||||
size_t n_except_fds,
|
||||
ForkFlags flags,
|
||||
PidRef *ret_pid) {
|
||||
@@ -1760,7 +1773,7 @@ int pidref_safe_fork_full(
|
||||
int namespace_fork(
|
||||
const char *outer_name,
|
||||
const char *inner_name,
|
||||
const int except_fds[],
|
||||
int except_fds[],
|
||||
size_t n_except_fds,
|
||||
ForkFlags flags,
|
||||
int pidns_fd,
|
||||
|
||||
@@ -185,12 +185,13 @@ typedef enum ForkFlags {
|
||||
FORK_KEEP_NOTIFY_SOCKET = 1 << 17, /* Unless this specified, $NOTIFY_SOCKET will be unset. */
|
||||
FORK_DETACH = 1 << 18, /* Double fork if needed to ensure PID1/subreaper is parent */
|
||||
FORK_NEW_NETNS = 1 << 19, /* Run child in its own network namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */
|
||||
FORK_PACK_FDS = 1 << 20, /* Rearrange the passed FDs to be FD 3,4,5,etc. Updates the array in place (combine with FORK_CLOSE_ALL_FDS!) */
|
||||
} ForkFlags;
|
||||
|
||||
int safe_fork_full(
|
||||
const char *name,
|
||||
const int stdio_fds[3],
|
||||
const int except_fds[],
|
||||
int except_fds[],
|
||||
size_t n_except_fds,
|
||||
ForkFlags flags,
|
||||
pid_t *ret_pid);
|
||||
@@ -202,7 +203,7 @@ static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) {
|
||||
int pidref_safe_fork_full(
|
||||
const char *name,
|
||||
const int stdio_fds[3],
|
||||
const int except_fds[],
|
||||
int except_fds[],
|
||||
size_t n_except_fds,
|
||||
ForkFlags flags,
|
||||
PidRef *ret_pid);
|
||||
@@ -211,7 +212,18 @@ static inline int pidref_safe_fork(const char *name, ForkFlags flags, PidRef *re
|
||||
return pidref_safe_fork_full(name, NULL, NULL, 0, flags, ret_pid);
|
||||
}
|
||||
|
||||
int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid);
|
||||
int namespace_fork(
|
||||
const char *outer_name,
|
||||
const char *inner_name,
|
||||
int except_fds[],
|
||||
size_t n_except_fds,
|
||||
ForkFlags flags,
|
||||
int pidns_fd,
|
||||
int mntns_fd,
|
||||
int netns_fd,
|
||||
int userns_fd,
|
||||
int root_fd,
|
||||
pid_t *ret_pid);
|
||||
|
||||
int set_oom_score_adjust(int value);
|
||||
int get_oom_score_adjust(int *ret);
|
||||
|
||||
+1
-41
@@ -66,46 +66,6 @@
|
||||
|
||||
#define SNDBUF_SIZE (8*1024*1024)
|
||||
|
||||
static int shift_fds(int fds[], size_t n_fds) {
|
||||
if (n_fds <= 0)
|
||||
return 0;
|
||||
|
||||
/* Modifies the fds array! (sorts it) */
|
||||
|
||||
assert(fds);
|
||||
|
||||
for (int start = 0;;) {
|
||||
int restart_from = -1;
|
||||
|
||||
for (int i = start; i < (int) n_fds; i++) {
|
||||
int nfd;
|
||||
|
||||
/* Already at right index? */
|
||||
if (fds[i] == i+3)
|
||||
continue;
|
||||
|
||||
nfd = fcntl(fds[i], F_DUPFD, i + 3);
|
||||
if (nfd < 0)
|
||||
return -errno;
|
||||
|
||||
safe_close(fds[i]);
|
||||
fds[i] = nfd;
|
||||
|
||||
/* Hmm, the fd we wanted isn't free? Then
|
||||
* let's remember that and try again from here */
|
||||
if (nfd != i+3 && restart_from < 0)
|
||||
restart_from = i;
|
||||
}
|
||||
|
||||
if (restart_from < 0)
|
||||
break;
|
||||
|
||||
start = restart_from;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int flag_fds(
|
||||
const int fds[],
|
||||
size_t n_socket_fds,
|
||||
@@ -4900,7 +4860,7 @@ int exec_invoke(
|
||||
|
||||
r = close_all_fds(keep_fds, n_keep_fds);
|
||||
if (r >= 0)
|
||||
r = shift_fds(params->fds, n_fds);
|
||||
r = pack_fds(params->fds, n_fds);
|
||||
if (r >= 0)
|
||||
r = flag_fds(params->fds, n_socket_fds, n_fds, context->non_blocking);
|
||||
if (r < 0) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "dns-domain.h"
|
||||
#include "fd-util.h"
|
||||
#include "home-util.h"
|
||||
#include "libcrypt-util.h"
|
||||
#include "memory-util.h"
|
||||
@@ -9,6 +10,8 @@
|
||||
#include "strv.h"
|
||||
#include "user-util.h"
|
||||
|
||||
DEFINE_HASH_OPS_FULL(blob_fd_hash_ops, char, path_hash_func, path_compare, free, void, close_fd_ptr);
|
||||
|
||||
bool suitable_user_name(const char *name) {
|
||||
|
||||
/* Checks whether the specified name is suitable for management via homed. Note that client-side
|
||||
@@ -137,3 +140,7 @@ int bus_message_append_secret(sd_bus_message *m, UserRecord *secret) {
|
||||
const char *home_record_dir(void) {
|
||||
return secure_getenv("SYSTEMD_HOME_RECORD_DIR") ?: "/var/lib/systemd/home/";
|
||||
}
|
||||
|
||||
const char *home_system_blob_dir(void) {
|
||||
return secure_getenv("SYSTEMD_HOME_SYSTEM_BLOB_DIR") ?: "/var/cache/systemd/home/";
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "hash-funcs.h"
|
||||
#include "time-util.h"
|
||||
#include "user-record.h"
|
||||
|
||||
@@ -20,6 +21,8 @@
|
||||
/* This should be 83% right now, i.e. 100 of (100 + 20). Let's protect us against accidental changes. */
|
||||
assert_cc(USER_DISK_SIZE_DEFAULT_PERCENT == 83U);
|
||||
|
||||
extern const struct hash_ops blob_fd_hash_ops;
|
||||
|
||||
bool suitable_user_name(const char *name);
|
||||
int suitable_realm(const char *realm);
|
||||
int suitable_image_path(const char *path);
|
||||
@@ -35,3 +38,4 @@ int bus_message_append_secret(sd_bus_message *m, UserRecord *secret);
|
||||
#define HOME_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
|
||||
|
||||
const char *home_record_dir(void);
|
||||
const char *home_system_blob_dir(void);
|
||||
|
||||
+250
-6
@@ -12,7 +12,9 @@
|
||||
#include "cap-list.h"
|
||||
#include "capability-util.h"
|
||||
#include "cgroup-util.h"
|
||||
#include "copy.h"
|
||||
#include "creds-util.h"
|
||||
#include "dirent-util.h"
|
||||
#include "dns-domain.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
@@ -20,6 +22,7 @@
|
||||
#include "format-table.h"
|
||||
#include "fs-util.h"
|
||||
#include "glyph-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "home-util.h"
|
||||
#include "homectl-fido2.h"
|
||||
#include "homectl-pkcs11.h"
|
||||
@@ -40,8 +43,10 @@
|
||||
#include "process-util.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "spawn-polkit-agent.h"
|
||||
#include "terminal-util.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "uid-classification.h"
|
||||
#include "user-record.h"
|
||||
#include "user-record-password-quality.h"
|
||||
@@ -85,6 +90,9 @@ static enum {
|
||||
static uint64_t arg_capability_bounding_set = UINT64_MAX;
|
||||
static uint64_t arg_capability_ambient_set = UINT64_MAX;
|
||||
static bool arg_prompt_new_user = false;
|
||||
static char *arg_blob_dir = NULL;
|
||||
static bool arg_blob_clear = false;
|
||||
static Hashmap *arg_blob_files = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_identity_extra, json_variant_unrefp);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_this_machine, json_variant_unrefp);
|
||||
@@ -94,6 +102,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_identity_filter, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_identity_filter_rlimits, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_blob_dir, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_blob_files, hashmap_freep);
|
||||
|
||||
static const BusLocator *bus_mgr;
|
||||
|
||||
@@ -107,7 +117,10 @@ static bool identity_properties_specified(void) {
|
||||
!strv_isempty(arg_identity_filter) ||
|
||||
!strv_isempty(arg_identity_filter_rlimits) ||
|
||||
!strv_isempty(arg_pkcs11_token_uri) ||
|
||||
!strv_isempty(arg_fido2_device);
|
||||
!strv_isempty(arg_fido2_device) ||
|
||||
arg_blob_dir ||
|
||||
arg_blob_clear ||
|
||||
!hashmap_isempty(arg_blob_files);
|
||||
}
|
||||
|
||||
static int acquire_bus(sd_bus **bus) {
|
||||
@@ -1266,9 +1279,132 @@ static int acquire_new_password(
|
||||
}
|
||||
}
|
||||
|
||||
static int acquire_merged_blob_dir(UserRecord *hr, bool existing, Hashmap **ret) {
|
||||
_cleanup_free_ char *sys_blob_path = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
const char *src_blob_path, *filename;
|
||||
void *fd_ptr;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
HASHMAP_FOREACH_KEY(fd_ptr, filename, arg_blob_files) {
|
||||
_cleanup_free_ char *filename_dup = NULL;
|
||||
_cleanup_close_ int fd_dup = -EBADF;
|
||||
|
||||
filename_dup = strdup(filename);
|
||||
if (!filename_dup)
|
||||
return log_oom();
|
||||
|
||||
if (PTR_TO_FD(fd_ptr) != -EBADF) {
|
||||
fd_dup = fcntl(PTR_TO_FD(fd_ptr), F_DUPFD_CLOEXEC, 3);
|
||||
if (fd_dup < 0)
|
||||
return log_error_errno(errno, "Failed to duplicate fd of %s: %m", filename);
|
||||
}
|
||||
|
||||
r = hashmap_ensure_put(&blobs, &blob_fd_hash_ops, filename_dup, FD_TO_PTR(fd_dup));
|
||||
if (r < 0)
|
||||
return r;
|
||||
TAKE_PTR(filename_dup); /* Ownership transferred to hashmap */
|
||||
TAKE_FD(fd_dup);
|
||||
}
|
||||
|
||||
if (arg_blob_dir)
|
||||
src_blob_path = arg_blob_dir;
|
||||
else if (existing && !arg_blob_clear) {
|
||||
if (hr->blob_directory)
|
||||
src_blob_path = hr->blob_directory;
|
||||
else {
|
||||
/* This isn't technically a correct thing to do for generic user records,
|
||||
* so anyone looking at this code for reference shouldn't replicate it.
|
||||
* However, since homectl is tied to homed, this is OK. This adds robustness
|
||||
* for situations where the user record is coming directly from the CLI and
|
||||
* thus doesn't have a blobDirectory set */
|
||||
|
||||
sys_blob_path = path_join(home_system_blob_dir(), hr->user_name);
|
||||
if (!sys_blob_path)
|
||||
return log_oom();
|
||||
|
||||
src_blob_path = sys_blob_path;
|
||||
}
|
||||
} else
|
||||
goto nodir; /* Shortcut: no dir to merge with, so just return copy of arg_blob_files */
|
||||
|
||||
d = opendir(src_blob_path);
|
||||
if (!d)
|
||||
return log_error_errno(errno, "Failed to open %s: %m", src_blob_path);
|
||||
|
||||
FOREACH_DIRENT_ALL(de, d, return log_error_errno(errno, "Failed to read %s: %m", src_blob_path)) {
|
||||
_cleanup_free_ char *name = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
|
||||
if (dot_or_dot_dot(de->d_name))
|
||||
continue;
|
||||
|
||||
if (hashmap_contains(blobs, de->d_name))
|
||||
continue; /* arg_blob_files should override the base dir */
|
||||
|
||||
if (!suitable_blob_filename(de->d_name)) {
|
||||
log_warning("File %s in blob directory %s has an invalid filename. Skipping.", de->d_name, src_blob_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
name = strdup(de->d_name);
|
||||
if (!name)
|
||||
return log_oom();
|
||||
|
||||
fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NOCTTY);
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to open %s in %s: %m", de->d_name, src_blob_path);
|
||||
|
||||
r = fd_verify_regular(fd);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Entry %s in blob directory %s is not a regular file. Skipping.", de->d_name, src_blob_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = hashmap_ensure_put(&blobs, &blob_fd_hash_ops, name, FD_TO_PTR(fd));
|
||||
if (r < 0)
|
||||
return r;
|
||||
TAKE_PTR(name); /* Ownership transferred to hashmap */
|
||||
TAKE_FD(fd);
|
||||
}
|
||||
|
||||
nodir:
|
||||
*ret = TAKE_PTR(blobs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bus_message_append_blobs(sd_bus_message *m, Hashmap *blobs) {
|
||||
const char *filename;
|
||||
void *fd_ptr;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
r = sd_bus_message_open_container(m, 'a', "{sh}");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
HASHMAP_FOREACH_KEY(fd_ptr, filename, blobs) {
|
||||
int fd = PTR_TO_FD(fd_ptr);
|
||||
|
||||
if (fd == -EBADF) /* File marked for deletion */
|
||||
continue;
|
||||
|
||||
r = sd_bus_message_append(m, "{sh}", filename, fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return sd_bus_message_close_container(m);
|
||||
}
|
||||
|
||||
static int create_home_common(JsonVariant *input) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
||||
int r;
|
||||
|
||||
r = acquire_bus(&bus);
|
||||
@@ -1281,6 +1417,10 @@ static int create_home_common(JsonVariant *input) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = acquire_merged_blob_dir(hr, false, &blobs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If the JSON record carries no plain text password (besides the recovery key), then let's query it
|
||||
* manually. */
|
||||
if (strv_length(hr->password) <= arg_recovery_key) {
|
||||
@@ -1327,7 +1467,7 @@ static int create_home_common(JsonVariant *input) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format user record: %m");
|
||||
|
||||
r = bus_message_new_method_call(bus, &m, bus_mgr, "CreateHome");
|
||||
r = bus_message_new_method_call(bus, &m, bus_mgr, "CreateHomeEx");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
@@ -1337,6 +1477,14 @@ static int create_home_common(JsonVariant *input) {
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = bus_message_append_blobs(m, blobs);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(m, "t", 0);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
|
||||
if (r < 0) {
|
||||
if (sd_bus_error_has_name(&error, BUS_ERROR_LOW_PASSWORD_QUALITY)) {
|
||||
@@ -1492,7 +1640,7 @@ static int acquire_updated_home_record(
|
||||
|
||||
reply = sd_bus_message_unref(reply);
|
||||
|
||||
r = json_variant_filter(&json, STRV_MAKE("binding", "status", "signature"));
|
||||
r = json_variant_filter(&json, STRV_MAKE("binding", "status", "signature", "blobManifest"));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to strip binding and status from record to update: %m");
|
||||
}
|
||||
@@ -1562,6 +1710,7 @@ static int update_home(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL, *secret = NULL;
|
||||
_cleanup_free_ char *buffer = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
||||
const char *username;
|
||||
int r;
|
||||
|
||||
@@ -1595,6 +1744,10 @@ static int update_home(int argc, char *argv[], void *userdata) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = acquire_merged_blob_dir(hr, true, &blobs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If we do multiple operations, let's output things more verbosely, since otherwise the repeated
|
||||
* authentication might be confusing. */
|
||||
|
||||
@@ -1606,7 +1759,7 @@ static int update_home(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_free_ char *formatted = NULL;
|
||||
|
||||
r = bus_message_new_method_call(bus, &m, bus_mgr, "UpdateHome");
|
||||
r = bus_message_new_method_call(bus, &m, bus_mgr, "UpdateHomeEx");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
@@ -1620,6 +1773,14 @@ static int update_home(int argc, char *argv[], void *userdata) {
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = bus_message_append_blobs(m, blobs);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(m, "t", 0);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
|
||||
if (r < 0) {
|
||||
if (arg_and_change_password &&
|
||||
@@ -2451,7 +2612,13 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
" Whether to require user verification to unlock\n"
|
||||
" the account\n"
|
||||
" --recovery-key=BOOL Add a recovery key\n"
|
||||
"\n%4$sAccount Management User Record Properties:%5$s\n"
|
||||
"\n%4$sBlob Directory User Record Properties:%5$s\n"
|
||||
" -b --blob=[FILENAME=]PATH\n"
|
||||
" Path to a replacement blob directory, or replace\n"
|
||||
" an individual files in the blob directory.\n"
|
||||
" --avatar=PATH Path to user avatar picture\n"
|
||||
" --login-background=PATH Path to user login background picture\n"
|
||||
"\n%4$sAccount Management User Record Properties:%5$s\n"
|
||||
" --locked=BOOL Set locked account state\n"
|
||||
" --not-before=TIMESTAMP Do not allow logins before\n"
|
||||
" --not-after=TIMESTAMP Do not allow logins after\n"
|
||||
@@ -2626,6 +2793,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_CAPABILITY_BOUNDING_SET,
|
||||
ARG_CAPABILITY_AMBIENT_SET,
|
||||
ARG_PROMPT_NEW_USER,
|
||||
ARG_AVATAR,
|
||||
ARG_LOGIN_BACKGROUND,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@@ -2719,6 +2888,9 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "capability-bounding-set", required_argument, NULL, ARG_CAPABILITY_BOUNDING_SET },
|
||||
{ "capability-ambient-set", required_argument, NULL, ARG_CAPABILITY_AMBIENT_SET },
|
||||
{ "prompt-new-user", no_argument, NULL, ARG_PROMPT_NEW_USER },
|
||||
{ "blob", required_argument, NULL, 'b' },
|
||||
{ "avatar", required_argument, NULL, ARG_AVATAR },
|
||||
{ "login-background", required_argument, NULL, ARG_LOGIN_BACKGROUND },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -2730,7 +2902,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
for (;;) {
|
||||
int c;
|
||||
|
||||
c = getopt_long(argc, argv, "hH:M:I:c:d:u:k:s:e:G:jPE", options, NULL);
|
||||
c = getopt_long(argc, argv, "hH:M:I:c:d:u:G:k:s:e:b:jPE", options, NULL);
|
||||
if (c < 0)
|
||||
break;
|
||||
|
||||
@@ -4027,6 +4199,78 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
arg_prompt_new_user = true;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
case ARG_AVATAR:
|
||||
case ARG_LOGIN_BACKGROUND: {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
_cleanup_free_ char *path = NULL, *filename = NULL;
|
||||
|
||||
if (c == 'b') {
|
||||
char *eq;
|
||||
|
||||
if (isempty(optarg)) { /* --blob= deletes everything, including existing blob dirs */
|
||||
hashmap_clear(arg_blob_files);
|
||||
arg_blob_dir = mfree(arg_blob_dir);
|
||||
arg_blob_clear = true;
|
||||
break;
|
||||
}
|
||||
|
||||
eq = strrchr(optarg, '=');
|
||||
if (!eq) { /* --blob=/some/path replaces the blob dir */
|
||||
r = parse_path_argument(optarg, false, &arg_blob_dir);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse path %s: %m", optarg);
|
||||
break;
|
||||
}
|
||||
|
||||
/* --blob=filename=/some/path replaces the file "filename" with /some/path */
|
||||
filename = strndup(optarg, eq - optarg);
|
||||
if (!filename)
|
||||
return log_oom();
|
||||
|
||||
if (isempty(filename))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse blob file assignment: %s", optarg);
|
||||
if (!suitable_blob_filename(filename))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid blob filename: %s", filename);
|
||||
|
||||
r = parse_path_argument(eq + 1, false, &path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse path %s: %m", eq + 1);
|
||||
} else {
|
||||
const char *well_known_filename =
|
||||
c == ARG_AVATAR ? "avatar" :
|
||||
c == ARG_LOGIN_BACKGROUND ? "login-background" :
|
||||
NULL;
|
||||
assert(well_known_filename);
|
||||
|
||||
filename = strdup(well_known_filename);
|
||||
if (!filename)
|
||||
return log_oom();
|
||||
|
||||
r = parse_path_argument(optarg, false, &path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse path %s: %m", optarg);
|
||||
}
|
||||
|
||||
if (path) {
|
||||
fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to open %s: %m", path);
|
||||
|
||||
if (fd_verify_regular(fd) < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provided blob is not a regular file: %s", path);
|
||||
} else
|
||||
fd = -EBADF; /* Delete the file */
|
||||
|
||||
r = hashmap_ensure_put(&arg_blob_files, &blob_fd_hash_ops, filename, FD_TO_PTR(fd));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to map %s to %s in blob directory: %m", path, filename);
|
||||
TAKE_PTR(filename); /* hashmap takes ownership */
|
||||
TAKE_FD(fd);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "fd-util.h"
|
||||
#include "home-util.h"
|
||||
#include "homed-bus.h"
|
||||
#include "missing_fcntl.h"
|
||||
#include "stat-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *error) {
|
||||
@@ -64,3 +68,72 @@ int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, U
|
||||
*ret = TAKE_PTR(hr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bus_message_read_blobs(sd_bus_message *m, Hashmap **ret, sd_bus_error *error) {
|
||||
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(ret);
|
||||
|
||||
/* We want to differentiate between blobs being NULL (not passed at all)
|
||||
* and empty (passed from dbus, but it was empty) */
|
||||
r = hashmap_ensure_allocated(&blobs, &blob_fd_hash_ops);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_enter_container(m, 'a', "{sh}");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *filename = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
const char *_filename = NULL;
|
||||
int _fd, flags;
|
||||
|
||||
r = sd_bus_message_read(m, "{sh}", &_filename, &_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
filename = strdup(_filename);
|
||||
if (!filename)
|
||||
return -ENOMEM;
|
||||
|
||||
fd = fcntl(_fd, F_DUPFD_CLOEXEC, 3);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
r = suitable_blob_filename(filename);
|
||||
if (r < 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid blob directory filename: %s", filename);
|
||||
|
||||
r = fd_verify_regular(fd);
|
||||
if (r < 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "FD for %s is not a regular file", filename);
|
||||
|
||||
flags = fcntl(fd, F_GETFL);
|
||||
if (flags < 0)
|
||||
return -errno;
|
||||
|
||||
/* Refuse fds w/ unexpected flags set. In particular, we don't want to permit O_PATH FDs, since
|
||||
* those don't actually guarentee that the client has access to the file. */
|
||||
if ((flags & ~(O_ACCMODE|RAW_O_LARGEFILE)) != 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "FD for %s has unexpected flags set", filename);
|
||||
|
||||
r = hashmap_put(blobs, filename, FD_TO_PTR(fd));
|
||||
if (r < 0)
|
||||
return r;
|
||||
TAKE_PTR(filename); /* Ownership transferred to hashmap */
|
||||
TAKE_FD(fd);
|
||||
}
|
||||
|
||||
r = sd_bus_message_exit_container(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(blobs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "user-record.h"
|
||||
#include "hashmap.h"
|
||||
#include "json.h"
|
||||
#include "user-record.h"
|
||||
|
||||
int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *error);
|
||||
int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, UserRecord **ret, sd_bus_error *error);
|
||||
int bus_message_read_blobs(sd_bus_message *m, Hashmap **ret, sd_bus_error *error);
|
||||
|
||||
@@ -295,7 +295,7 @@ int bus_home_method_realize(
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = home_create(h, secret, error);
|
||||
r = home_create(h, secret, NULL, 0, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -416,7 +416,13 @@ int bus_home_method_authenticate(
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *hr, sd_bus_error *error) {
|
||||
int bus_home_method_update_record(
|
||||
Home *h,
|
||||
sd_bus_message *message,
|
||||
UserRecord *hr,
|
||||
Hashmap *blobs,
|
||||
uint64_t flags,
|
||||
sd_bus_error *error) {
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
@@ -427,6 +433,9 @@ int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (flags != 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Provided flags are unsupported.");
|
||||
|
||||
r = home_verify_polkit_async(
|
||||
h,
|
||||
message,
|
||||
@@ -438,7 +447,7 @@ int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = home_update(h, hr, error);
|
||||
r = home_update(h, hr, blobs, flags, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -458,6 +467,8 @@ int bus_home_method_update(
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
||||
uint64_t flags = 0;
|
||||
Home *h = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
@@ -467,7 +478,17 @@ int bus_home_method_update(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return bus_home_method_update_record(h, message, hr, error);
|
||||
if (endswith(sd_bus_message_get_member(message), "Ex")) {
|
||||
r = bus_message_read_blobs(message, &blobs, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_read(message, "t", &flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return bus_home_method_update_record(h, message, hr, blobs, flags, error);
|
||||
}
|
||||
|
||||
int bus_home_method_resize(
|
||||
@@ -892,6 +913,11 @@ const sd_bus_vtable home_vtable[] = {
|
||||
SD_BUS_NO_RESULT,
|
||||
bus_home_method_update,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD_WITH_ARGS("UpdateEx",
|
||||
SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags),
|
||||
SD_BUS_NO_RESULT,
|
||||
bus_home_method_update,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD_WITH_ARGS("Resize",
|
||||
SD_BUS_ARGS("t", size, "s", secret),
|
||||
SD_BUS_NO_RESULT,
|
||||
|
||||
@@ -17,7 +17,7 @@ int bus_home_method_remove(sd_bus_message *message, void *userdata, sd_bus_error
|
||||
int bus_home_method_fixate(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_authenticate(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_update(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_update_record(Home *home, sd_bus_message *message, UserRecord *hr, sd_bus_error *error);
|
||||
int bus_home_method_update_record(Home *home, sd_bus_message *message, UserRecord *hr, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
|
||||
int bus_home_method_resize(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_change_password(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_lock(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
|
||||
+98
-21
@@ -33,6 +33,7 @@
|
||||
#include "process-util.h"
|
||||
#include "quota-util.h"
|
||||
#include "resize-fs.h"
|
||||
#include "rm-rf.h"
|
||||
#include "set.h"
|
||||
#include "signal-util.h"
|
||||
#include "stat-util.h"
|
||||
@@ -54,7 +55,13 @@
|
||||
assert_cc(HOME_UID_MIN <= HOME_UID_MAX);
|
||||
assert_cc(HOME_USERS_MAX <= (HOME_UID_MAX - HOME_UID_MIN + 1));
|
||||
|
||||
static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord *secret);
|
||||
static int home_start_work(
|
||||
Home *h,
|
||||
const char *verb,
|
||||
UserRecord *hr,
|
||||
UserRecord *secret,
|
||||
Hashmap *blobs,
|
||||
uint64_t flags);
|
||||
|
||||
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(operation_hash_ops, void, trivial_hash_func, trivial_compare_func, Operation, operation_unref);
|
||||
|
||||
@@ -96,7 +103,7 @@ static int suitable_home_record(UserRecord *hr) {
|
||||
|
||||
int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) {
|
||||
_cleanup_(home_freep) Home *home = NULL;
|
||||
_cleanup_free_ char *nm = NULL, *ns = NULL;
|
||||
_cleanup_free_ char *nm = NULL, *ns = NULL, *blob = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
@@ -162,6 +169,13 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
blob = path_join(home_system_blob_dir(), hr->user_name);
|
||||
if (!blob)
|
||||
return -ENOMEM;
|
||||
r = mkdir_safe(blob, 0755, 0, 0, MKDIR_IGNORE_EXISTING);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to create blob dir for user '%s': %m", home->user_name);
|
||||
|
||||
(void) bus_manager_emit_auto_login_changed(m);
|
||||
(void) bus_home_emit_change(home);
|
||||
(void) manager_schedule_rebalance(m, /* immediately= */ false);
|
||||
@@ -323,7 +337,9 @@ int home_save_record(Home *h) {
|
||||
}
|
||||
|
||||
int home_unlink_record(Home *h) {
|
||||
_cleanup_free_ char *blob = NULL;
|
||||
const char *fn;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
|
||||
@@ -335,6 +351,13 @@ int home_unlink_record(Home *h) {
|
||||
if (unlink(fn) < 0 && errno != ENOENT)
|
||||
return -errno;
|
||||
|
||||
blob = path_join(home_system_blob_dir(), h->user_name);
|
||||
if (!blob)
|
||||
return -ENOMEM;
|
||||
r = rm_rf(blob, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -721,7 +744,7 @@ static void home_fixate_finish(Home *h, int ret, UserRecord *hr) {
|
||||
|
||||
if (IN_SET(h->state, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE)) {
|
||||
|
||||
r = home_start_work(h, "activate", h->record, secret);
|
||||
r = home_start_work(h, "activate", h->record, secret, NULL, 0);
|
||||
if (r < 0) {
|
||||
h->current_operation = operation_result_unref(h->current_operation, r, NULL);
|
||||
home_set_state(h, _HOME_STATE_INVALID);
|
||||
@@ -1161,10 +1184,17 @@ static int home_on_worker_process(sd_event_source *s, const siginfo_t *si, void
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord *secret) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
static int home_start_work(
|
||||
Home *h,
|
||||
const char *verb,
|
||||
UserRecord *hr,
|
||||
UserRecord *secret,
|
||||
Hashmap *blobs,
|
||||
uint64_t flags) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *fdmap = NULL;
|
||||
_cleanup_(erase_and_freep) char *formatted = NULL;
|
||||
_cleanup_close_ int stdin_fd = -EBADF, stdout_fd = -EBADF;
|
||||
_cleanup_free_ int *blob_fds = NULL;
|
||||
pid_t pid = 0;
|
||||
int r;
|
||||
|
||||
@@ -1192,6 +1222,37 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord
|
||||
return r;
|
||||
}
|
||||
|
||||
if (blobs) {
|
||||
const char *blob_filename = NULL;
|
||||
void *fd_ptr;
|
||||
size_t i = 0;
|
||||
|
||||
blob_fds = new(int, hashmap_size(blobs));
|
||||
if (!blob_fds)
|
||||
return -ENOMEM;
|
||||
|
||||
/* homework needs to be able to tell the difference between blobs being null
|
||||
* (the fdmap field is completely missing) and it being empty (the field is an
|
||||
* empty object) */
|
||||
r = json_variant_new_object(&fdmap, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
HASHMAP_FOREACH_KEY(fd_ptr, blob_filename, blobs) {
|
||||
blob_fds[i] = PTR_TO_FD(fd_ptr);
|
||||
|
||||
r = json_variant_set_field_integer(&fdmap, blob_filename, i);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
r = json_variant_set_field(&v, HOMEWORK_BLOB_FDMAP_FIELD, fdmap);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = json_variant_format(v, 0, &formatted);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1208,8 +1269,9 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord
|
||||
|
||||
r = safe_fork_full("(sd-homework)",
|
||||
(int[]) { stdin_fd, stdout_fd, STDERR_FILENO },
|
||||
NULL, 0,
|
||||
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid);
|
||||
blob_fds, hashmap_size(blobs),
|
||||
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_CLOEXEC_OFF|FORK_PACK_FDS|FORK_DEATHSIG_SIGTERM|
|
||||
FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
@@ -1254,6 +1316,10 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m");
|
||||
|
||||
r = setenv_systemd_log_level();
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to update $SYSTEMD_LOG_LEVEL, ignoring: %m");
|
||||
|
||||
/* Allow overriding the homework path via an environment variable, to make debugging
|
||||
* easier. */
|
||||
homework = getenv("SYSTEMD_HOMEWORK_PATH") ?: SYSTEMD_HOMEWORK_PATH;
|
||||
@@ -1326,7 +1392,7 @@ static int home_fixate_internal(
|
||||
assert(secret);
|
||||
assert(IN_SET(for_state, HOME_FIXATING, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE));
|
||||
|
||||
r = home_start_work(h, "inspect", h->record, secret);
|
||||
r = home_start_work(h, "inspect", h->record, secret, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1375,7 +1441,7 @@ static int home_activate_internal(Home *h, UserRecord *secret, HomeState for_sta
|
||||
assert(secret);
|
||||
assert(IN_SET(for_state, HOME_ACTIVATING, HOME_ACTIVATING_FOR_ACQUIRE));
|
||||
|
||||
r = home_start_work(h, "activate", h->record, secret);
|
||||
r = home_start_work(h, "activate", h->record, secret, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1427,7 +1493,7 @@ static int home_authenticate_internal(Home *h, UserRecord *secret, HomeState for
|
||||
assert(secret);
|
||||
assert(IN_SET(for_state, HOME_AUTHENTICATING, HOME_AUTHENTICATING_WHILE_ACTIVE, HOME_AUTHENTICATING_FOR_ACQUIRE));
|
||||
|
||||
r = home_start_work(h, "inspect", h->record, secret);
|
||||
r = home_start_work(h, "inspect", h->record, secret, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1472,7 +1538,7 @@ static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) {
|
||||
|
||||
home_unpin(h); /* unpin so that we can deactivate */
|
||||
|
||||
r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL);
|
||||
r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL, NULL, 0);
|
||||
if (r < 0)
|
||||
/* Operation failed before it even started, reacquire pin fd, if state still dictates so */
|
||||
home_update_pin_fd(h, _HOME_STATE_INVALID);
|
||||
@@ -1509,7 +1575,7 @@ int home_deactivate(Home *h, bool force, sd_bus_error *error) {
|
||||
return home_deactivate_internal(h, force, error);
|
||||
}
|
||||
|
||||
int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
|
||||
int home_create(Home *h, UserRecord *secret, Hashmap *blobs, uint64_t flags, sd_bus_error *error) {
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
@@ -1552,7 +1618,7 @@ int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
|
||||
return r;
|
||||
}
|
||||
|
||||
r = home_start_work(h, "create", h->record, secret);
|
||||
r = home_start_work(h, "create", h->record, secret, blobs, flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1582,7 +1648,7 @@ int home_remove(Home *h, sd_bus_error *error) {
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
|
||||
}
|
||||
|
||||
r = home_start_work(h, "remove", h->record, NULL);
|
||||
r = home_start_work(h, "remove", h->record, NULL, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1626,6 +1692,8 @@ static int home_update_internal(
|
||||
const char *verb,
|
||||
UserRecord *hr,
|
||||
UserRecord *secret,
|
||||
Hashmap *blobs,
|
||||
uint64_t flags,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_hr = NULL, *saved_secret = NULL, *signed_hr = NULL;
|
||||
@@ -1649,6 +1717,15 @@ static int home_update_internal(
|
||||
secret = saved_secret;
|
||||
}
|
||||
|
||||
if (blobs) {
|
||||
const char *failed = NULL;
|
||||
r = user_record_ensure_blob_manifest(hr, blobs, &failed);
|
||||
if (r == -EINVAL)
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest.");
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed));
|
||||
}
|
||||
|
||||
r = manager_verify_user_record(h->manager, hr);
|
||||
switch (r) {
|
||||
|
||||
@@ -1691,14 +1768,14 @@ static int home_update_internal(
|
||||
return sd_bus_error_set(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Home record different but timestamp remained the same, refusing.");
|
||||
}
|
||||
|
||||
r = home_start_work(h, verb, new_hr, secret);
|
||||
r = home_start_work(h, verb, new_hr, secret, blobs, flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
|
||||
int home_update(Home *h, UserRecord *hr, Hashmap *blobs, uint64_t flags, sd_bus_error *error) {
|
||||
HomeState state;
|
||||
int r;
|
||||
|
||||
@@ -1726,7 +1803,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_update_internal(h, "update", hr, NULL, error);
|
||||
r = home_update_internal(h, "update", hr, NULL, blobs, flags, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1813,7 +1890,7 @@ int home_resize(Home *h,
|
||||
c = TAKE_PTR(signed_c);
|
||||
}
|
||||
|
||||
r = home_update_internal(h, automatic ? "resize-auto" : "resize", c, secret, error);
|
||||
r = home_update_internal(h, automatic ? "resize-auto" : "resize", c, secret, NULL, 0, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1929,7 +2006,7 @@ int home_passwd(Home *h,
|
||||
return r;
|
||||
}
|
||||
|
||||
r = home_update_internal(h, "passwd", signed_c, merged_secret, error);
|
||||
r = home_update_internal(h, "passwd", signed_c, merged_secret, NULL, 0, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1986,7 +2063,7 @@ int home_lock(Home *h, sd_bus_error *error) {
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
|
||||
}
|
||||
|
||||
r = home_start_work(h, "lock", h->record, NULL);
|
||||
r = home_start_work(h, "lock", h->record, NULL, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -2001,7 +2078,7 @@ static int home_unlock_internal(Home *h, UserRecord *secret, HomeState for_state
|
||||
assert(secret);
|
||||
assert(IN_SET(for_state, HOME_UNLOCKING, HOME_UNLOCKING_FOR_ACQUIRE));
|
||||
|
||||
r = home_start_work(h, "unlock", h->record, secret);
|
||||
r = home_start_work(h, "unlock", h->record, secret, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
typedef struct Home Home;
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "homed-manager.h"
|
||||
#include "homed-operation.h"
|
||||
#include "list.h"
|
||||
@@ -193,9 +194,9 @@ int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error);
|
||||
int home_activate(Home *h, bool if_referenced, UserRecord *secret, sd_bus_error *error);
|
||||
int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error);
|
||||
int home_deactivate(Home *h, bool force, sd_bus_error *error);
|
||||
int home_create(Home *h, UserRecord *secret, sd_bus_error *error);
|
||||
int home_create(Home *h, UserRecord *secret, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
|
||||
int home_remove(Home *h, sd_bus_error *error);
|
||||
int home_update(Home *h, UserRecord *new_record, sd_bus_error *error);
|
||||
int home_update(Home *h, UserRecord *new_record, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
|
||||
int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, bool automatic, sd_bus_error *error);
|
||||
int home_passwd(Home *h, UserRecord *new_secret, UserRecord *old_secret, sd_bus_error *error);
|
||||
int home_unregister(Home *h, sd_bus_error *error);
|
||||
|
||||
@@ -340,7 +340,7 @@ static int method_deactivate_home(sd_bus_message *message, void *userdata, sd_bu
|
||||
return generic_home_method(userdata, message, bus_home_method_deactivate, error);
|
||||
}
|
||||
|
||||
static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd_bus_error *error) {
|
||||
static int validate_and_allocate_home(Manager *m, UserRecord *hr, Hashmap *blobs, Home **ret, sd_bus_error *error) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL;
|
||||
bool signed_locally;
|
||||
Home *other;
|
||||
@@ -370,6 +370,15 @@ static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd
|
||||
if (r != -ESRCH)
|
||||
return r;
|
||||
|
||||
if (blobs) {
|
||||
const char *failed = NULL;
|
||||
r = user_record_ensure_blob_manifest(hr, blobs, &failed);
|
||||
if (r == -EINVAL)
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest.");
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed));
|
||||
}
|
||||
|
||||
r = manager_verify_user_record(m, hr);
|
||||
switch (r) {
|
||||
|
||||
@@ -458,7 +467,7 @@ static int method_register_home(
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = validate_and_allocate_home(m, hr, &h, error);
|
||||
r = validate_and_allocate_home(m, hr, NULL, &h, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -475,12 +484,11 @@ static int method_unregister_home(sd_bus_message *message, void *userdata, sd_bu
|
||||
return generic_home_method(userdata, message, bus_home_method_unregister, error);
|
||||
}
|
||||
|
||||
static int method_create_home(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
static int method_create_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
||||
uint64_t flags = 0;
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
Home *h;
|
||||
int r;
|
||||
@@ -491,6 +499,18 @@ static int method_create_home(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (endswith(sd_bus_message_get_member(message), "Ex")) {
|
||||
r = bus_message_read_blobs(message, &blobs, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_read(message, "t", &flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (flags != 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Provided flags are unsupported.");
|
||||
}
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
"org.freedesktop.home1.create-home",
|
||||
@@ -502,11 +522,11 @@ static int method_create_home(
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = validate_and_allocate_home(m, hr, &h, error);
|
||||
r = validate_and_allocate_home(m, hr, blobs, &h, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_create(h, hr, error);
|
||||
r = home_create(h, hr, blobs, flags, error);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
@@ -544,6 +564,8 @@ static int method_authenticate_home(sd_bus_message *message, void *userdata, sd_
|
||||
|
||||
static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
||||
uint64_t flags = 0;
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
Home *h;
|
||||
int r;
|
||||
@@ -554,13 +576,23 @@ static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_er
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (endswith(sd_bus_message_get_member(message), "Ex")) {
|
||||
r = bus_message_read_blobs(message, &blobs, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_read(message, "t", &flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
assert(hr->user_name);
|
||||
|
||||
h = hashmap_get(m->homes_by_name, hr->user_name);
|
||||
if (!h)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", hr->user_name);
|
||||
|
||||
return bus_home_method_update_record(h, message, hr, error);
|
||||
return bus_home_method_update_record(h, message, hr, blobs, flags, error);
|
||||
}
|
||||
|
||||
static int method_resize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
@@ -769,6 +801,11 @@ static const sd_bus_vtable manager_vtable[] = {
|
||||
SD_BUS_NO_RESULT,
|
||||
method_create_home,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD_WITH_ARGS("CreateHomeEx",
|
||||
SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags),
|
||||
SD_BUS_NO_RESULT,
|
||||
method_create_home,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
|
||||
/* Create $HOME for already registered JSON entry */
|
||||
SD_BUS_METHOD_WITH_ARGS("RealizeHome",
|
||||
@@ -804,6 +841,11 @@ static const sd_bus_vtable manager_vtable[] = {
|
||||
SD_BUS_NO_RESULT,
|
||||
method_update_home,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD_WITH_ARGS("UpdateHomeEx",
|
||||
SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags),
|
||||
SD_BUS_NO_RESULT,
|
||||
method_update_home,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
|
||||
SD_BUS_METHOD_WITH_ARGS("ResizeHome",
|
||||
SD_BUS_ARGS("s", user_name, "t", size, "s", secret),
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "quota-util.h"
|
||||
#include "random-util.h"
|
||||
#include "resize-fs.h"
|
||||
#include "rm-rf.h"
|
||||
#include "socket-util.h"
|
||||
#include "sort-util.h"
|
||||
#include "stat-util.h"
|
||||
@@ -79,6 +80,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_sysfs_hash_ops, char, pat
|
||||
|
||||
static int on_home_inotify(sd_event_source *s, const struct inotify_event *event, void *userdata);
|
||||
static int manager_gc_images(Manager *m);
|
||||
static int manager_gc_blob(Manager *m);
|
||||
static int manager_enumerate_images(Manager *m);
|
||||
static int manager_assess_image(Manager *m, int dir_fd, const char *dir_path, const char *dentry_name);
|
||||
static void manager_revalidate_image(Manager *m, Home *h);
|
||||
@@ -1627,6 +1629,9 @@ int manager_startup(Manager *m) {
|
||||
/* Let's clean up home directories whose devices got removed while we were not running */
|
||||
(void) manager_enqueue_gc(m, NULL);
|
||||
|
||||
/* Let's clean up blob directories for home dirs that no longer exist */
|
||||
(void) manager_gc_blob(m);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1715,6 +1720,29 @@ int manager_gc_images(Manager *m) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_gc_blob(Manager *m) {
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
d = opendir(home_system_blob_dir());
|
||||
if (!d) {
|
||||
if (errno == ENOENT)
|
||||
return 0;
|
||||
return log_error_errno(errno, "Failed to open %s: %m", home_system_blob_dir());
|
||||
}
|
||||
|
||||
FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read system blob directory: %m"))
|
||||
if (!hashmap_contains(m->homes_by_name, de->d_name)) {
|
||||
r = rm_rf_at(dirfd(d), de->d_name, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to delete blob dir for missing user '%s', ignoring: %m", de->d_name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_deferred_rescan(sd_event_source *s, void *userdata) {
|
||||
Manager *m = ASSERT_PTR(userdata);
|
||||
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "copy.h"
|
||||
#include "fileio.h"
|
||||
#include "fd-util.h"
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "home-util.h"
|
||||
#include "homework-blob.h"
|
||||
#include "homework.h"
|
||||
#include "install-file.h"
|
||||
#include "macro.h"
|
||||
#include "path-util.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "rm-rf.h"
|
||||
#include "sha256.h"
|
||||
#include "string-util.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "umask-util.h"
|
||||
#include "utf8.h"
|
||||
|
||||
static int copy_one_blob(
|
||||
int src_fd,
|
||||
int dest_dfd,
|
||||
const char *name,
|
||||
uint64_t *total_size,
|
||||
uid_t uid,
|
||||
Hashmap *manifest) {
|
||||
_cleanup_(unlink_and_freep) char *dest_tmpname = NULL;
|
||||
_cleanup_close_ int dest = -EBADF;
|
||||
uint8_t hash[SHA256_DIGEST_SIZE], *known_hash;
|
||||
off_t initial, size;
|
||||
int r;
|
||||
|
||||
assert(src_fd >= 0);
|
||||
assert(dest_dfd >= 0);
|
||||
assert(name);
|
||||
assert(total_size);
|
||||
assert(uid_is_valid(uid));
|
||||
assert(manifest);
|
||||
|
||||
if (!suitable_blob_filename(name)) {
|
||||
log_warning("Blob %s has invalid filename. Skipping.", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
known_hash = hashmap_get(manifest, name);
|
||||
if (!known_hash) {
|
||||
log_warning("Blob %s is missing from manifest. Skipping.", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = fd_verify_regular(src_fd);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Blob %s is not a regular file. Skipping.", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
initial = lseek(src_fd, 0, SEEK_CUR);
|
||||
if (initial < 0)
|
||||
return log_debug_errno(errno, "Failed to get initial pos on fd for blob %s: %m", name);
|
||||
if (initial > 0)
|
||||
log_debug("Blob %s started offset %s into file", name, FORMAT_BYTES(initial));
|
||||
|
||||
/* Hashing is relatively cheaper compared to copying, especially since we're possibly copying across
|
||||
* filesystems or even devices here. So first we check the hash and bail early if the file's contents
|
||||
* don't match what's in the manifest. */
|
||||
|
||||
r = sha256_fd(src_fd, BLOB_DIR_MAX_SIZE, hash);
|
||||
if (r == -EFBIG)
|
||||
return log_warning_errno(r, "Blob %s is larger than blob directory size limit. Not copying any further.", name);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to compute sha256 for blob %s: %m", name);
|
||||
if (memcmp(hash, known_hash, SHA256_DIGEST_SIZE) != 0) {
|
||||
log_warning("Blob %s has incorrect hash. Skipping.", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size = lseek(src_fd, 0, SEEK_CUR);
|
||||
if (size < 0)
|
||||
return log_debug_errno(errno, "Failed to get final pos on fd for blob %s: %m", name);
|
||||
if (!DEC_SAFE(&size, initial))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid seek position on fd for %s. Couldn't get size.", name);
|
||||
|
||||
if (!INC_SAFE(total_size, size))
|
||||
*total_size = UINT64_MAX;
|
||||
log_debug("Blob %s size is %s, making the new total dir size %s", name, FORMAT_BYTES(size),
|
||||
*total_size != UINT64_MAX ? FORMAT_BYTES(*total_size) : "overflow!");
|
||||
if (*total_size > BLOB_DIR_MAX_SIZE)
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EFBIG),
|
||||
"Blob %s will cause blob directory to exceed its size limit. Not copying any further.", name);
|
||||
|
||||
/* Next we copy but don't yet link the file into the blob directory */
|
||||
|
||||
if (lseek(src_fd, initial, SEEK_SET) < 0)
|
||||
return log_debug_errno(errno, "Failed to rewind fd for blob %s: %m", name);
|
||||
|
||||
dest = open_tmpfile_linkable_at(dest_dfd, name, O_RDWR|O_CLOEXEC, &dest_tmpname);
|
||||
if (dest < 0)
|
||||
return log_debug_errno(dest, "Failed to create dest tmpfile for blob %s: %m", name);
|
||||
|
||||
if (fchmod(dest, 0644) < 0)
|
||||
return log_debug_errno(errno, "Failed to chmod blob %s: %m", name);
|
||||
if (fchown(dest, uid, uid) < 0)
|
||||
return log_debug_errno(errno, "Failed to chown blob %s: %m", name);
|
||||
|
||||
r = copy_bytes(src_fd, dest, BLOB_DIR_MAX_SIZE, 0);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to copy blob %s: %m", name);
|
||||
|
||||
/* The source FD might have changed while we were busy copying, thus invalidating the hash.
|
||||
* So, we re-hash the data we just copied to make sure that this didn't happen. */
|
||||
|
||||
if (lseek(dest, 0, SEEK_SET) < 0)
|
||||
return log_debug_errno(errno, "Failed to rewind blob %s for rehash: %m", name);
|
||||
|
||||
r = sha256_fd(dest, BLOB_DIR_MAX_SIZE, hash);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to rehash blob %s: %m", name);
|
||||
if (memcmp(hash, known_hash, SHA256_DIGEST_SIZE) != 0) {
|
||||
log_warning("Blob %s has changed while we were copying it. Skipping.", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The file's contents still match the blob manifest, so it's safe to expose it in the directory */
|
||||
|
||||
r = link_tmpfile_at(dest, dest_dfd, dest_tmpname, name, 0);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to link blob %s: %m", name);
|
||||
dest_tmpname = mfree(dest_tmpname);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int replace_blob_at(
|
||||
int src_base_dfd,
|
||||
const char *src_name,
|
||||
int dest_base_dfd,
|
||||
const char *dest_name,
|
||||
Hashmap *manifest,
|
||||
mode_t mode,
|
||||
uid_t uid) {
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
_cleanup_close_ int src_dfd = -EBADF, dest_dfd = -EBADF;
|
||||
_cleanup_free_ DirectoryEntries *de = NULL;
|
||||
uint64_t total_size = 0;
|
||||
int r;
|
||||
|
||||
assert(src_base_dfd >= 0);
|
||||
assert(src_name);
|
||||
assert(dest_base_dfd >= 0);
|
||||
assert(dest_name);
|
||||
assert(uid_is_valid(uid));
|
||||
|
||||
src_dfd = openat(src_base_dfd, src_name, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||
if (src_dfd < 0) {
|
||||
if (errno == ENOENT)
|
||||
return 0;
|
||||
return log_debug_errno(errno, "Failed to open src blob dir: %m");
|
||||
}
|
||||
|
||||
r = tempfn_random(dest_name, NULL, &fn);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
dest_dfd = open_mkdir_at(dest_base_dfd, fn, O_EXCL|O_CLOEXEC, mode);
|
||||
if (dest_dfd < 0)
|
||||
return log_debug_errno(dest_dfd, "Failed to create/open dest blob dir: %m");
|
||||
|
||||
r = readdir_all(src_dfd, RECURSE_DIR_SORT, &de);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to read src blob dir: %m");
|
||||
goto fail;
|
||||
}
|
||||
for (size_t i = 0; i < de->n_entries; i++) {
|
||||
const char *name = de->entries[i]->d_name;
|
||||
_cleanup_close_ int src_fd = -EBADF;
|
||||
|
||||
src_fd = openat(src_dfd, name, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
|
||||
if (src_fd < 0) {
|
||||
r = log_debug_errno(errno, "Failed to open %s in src blob dir: %m", name);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = copy_one_blob(src_fd, dest_dfd, name, &total_size, uid, manifest);
|
||||
if (r == -EFBIG)
|
||||
break;
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (fchown(dest_dfd, uid, uid) < 0) {
|
||||
r = log_debug_errno(errno, "Failed to chown dest blob dir: %m");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = install_file(dest_base_dfd, fn, dest_base_dfd, dest_name, INSTALL_REPLACE);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to move dest blob dir into place: %m");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
(void) rm_rf_at(dest_base_dfd, fn, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK);
|
||||
return r;
|
||||
}
|
||||
|
||||
int home_reconcile_blob_dirs(UserRecord *h, int root_fd, int reconciled) {
|
||||
_cleanup_close_ int sys_base_dfd = -EBADF;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(root_fd >= 0);
|
||||
assert(reconciled >= 0);
|
||||
|
||||
if (reconciled == USER_RECONCILE_IDENTICAL)
|
||||
return 0;
|
||||
|
||||
sys_base_dfd = open(home_system_blob_dir(), O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||
if (sys_base_dfd < 0)
|
||||
return log_error_errno(errno, "Failed to open system blob dir: %m");
|
||||
|
||||
if (reconciled == USER_RECONCILE_HOST_WON) {
|
||||
r = replace_blob_at(sys_base_dfd, h->user_name, root_fd, ".identity-blob",
|
||||
h->blob_manifest, 0700, h->uid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to replace embedded blobs with system blobs: %m");
|
||||
|
||||
log_info("Replaced embedded blob dir with contents of system blob dir.");
|
||||
} else {
|
||||
assert(reconciled == USER_RECONCILE_EMBEDDED_WON);
|
||||
|
||||
r = replace_blob_at(root_fd, ".identity-blob", sys_base_dfd, h->user_name,
|
||||
h->blob_manifest, 0755, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to replace system blobs with embedded blobs: %m");
|
||||
|
||||
log_info("Replaced system blob dir with contents of embedded blob dir.");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int home_apply_new_blob_dir(UserRecord *h, Hashmap *blobs) {
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
_cleanup_close_ int base_dfd = -EBADF, dfd = -EBADF;
|
||||
uint64_t total_size = 0;
|
||||
const char *filename;
|
||||
const void *v;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
|
||||
if (!blobs) /* Shortcut: If no blobs are passed from dbus, we have nothing to do. */
|
||||
return 0;
|
||||
|
||||
base_dfd = open(home_system_blob_dir(), O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||
if (base_dfd < 0)
|
||||
return log_error_errno(errno, "Failed to open system blob base dir: %m");
|
||||
|
||||
if (hashmap_isempty(blobs)) {
|
||||
/* Shortcut: If blobs was passed but empty, we can simply delete the contents
|
||||
* of the directory. */
|
||||
r = rm_rf_at(base_dfd, h->user_name, REMOVE_PHYSICAL|REMOVE_MISSING_OK);
|
||||
if (r < 0)
|
||||
return log_error_errno(errno, "Failed to empty out system blob dir: %m");
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = tempfn_random(h->user_name, NULL, &fn);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
dfd = open_mkdir_at(base_dfd, fn, O_EXCL|O_CLOEXEC, 0755);
|
||||
if (dfd < 0)
|
||||
return log_error_errno(errno, "Failed to create system blob dir: %m");
|
||||
|
||||
HASHMAP_FOREACH_KEY(v, filename, blobs) {
|
||||
r = copy_one_blob(PTR_TO_FD(v), dfd, filename, &total_size, 0, h->blob_manifest);
|
||||
if (r == -EFBIG)
|
||||
break;
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to copy %s into system blob dir: %m", filename);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
r = install_file(base_dfd, fn, base_dfd, h->user_name, INSTALL_REPLACE);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to move system blob dir into place: %m");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
log_info("Replaced system blob directory.");
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
(void) rm_rf_at(base_dfd, fn, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK);
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "user-record.h"
|
||||
|
||||
int home_reconcile_blob_dirs(UserRecord *h, int root_fd, int reconciled);
|
||||
|
||||
int home_apply_new_blob_dir(UserRecord *h, Hashmap *blobs);
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "btrfs-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "homework-blob.h"
|
||||
#include "homework-directory.h"
|
||||
#include "homework-mount.h"
|
||||
#include "homework-quota.h"
|
||||
@@ -265,7 +266,7 @@ int home_resize_directory(
|
||||
UserRecord **ret_home) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *new_home = NULL;
|
||||
int r;
|
||||
int r, reconciled;
|
||||
|
||||
assert(h);
|
||||
assert(setup);
|
||||
@@ -276,9 +277,9 @@ int home_resize_directory(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
reconciled = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
|
||||
if (reconciled < 0)
|
||||
return reconciled;
|
||||
|
||||
r = home_maybe_shift_uid(h, flags, setup);
|
||||
if (r < 0)
|
||||
@@ -294,6 +295,10 @@ int home_resize_directory(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_reconcile_blob_dirs(new_home, setup->root_fd, reconciled);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_extend_embedded_identity(new_home, h, setup);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "glyph-util.h"
|
||||
#include "gpt.h"
|
||||
#include "home-util.h"
|
||||
#include "homework-blob.h"
|
||||
#include "homework-luks.h"
|
||||
#include "homework-mount.h"
|
||||
#include "io-util.h"
|
||||
@@ -3129,7 +3130,7 @@ int home_resize_luks(
|
||||
struct fdisk_partition *partition = NULL;
|
||||
_cleanup_close_ int opened_image_fd = -EBADF;
|
||||
_cleanup_free_ char *whole_disk = NULL;
|
||||
int r, resize_type, image_fd = -EBADF;
|
||||
int r, resize_type, image_fd = -EBADF, reconciled = USER_RECONCILE_IDENTICAL;
|
||||
sd_id128_t disk_uuid;
|
||||
const char *ip, *ipo;
|
||||
struct statfs sfs;
|
||||
@@ -3235,9 +3236,9 @@ int home_resize_luks(
|
||||
return r;
|
||||
|
||||
if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) {
|
||||
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
reconciled = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
|
||||
if (reconciled < 0)
|
||||
return reconciled;
|
||||
}
|
||||
|
||||
r = home_maybe_shift_uid(h, flags, setup);
|
||||
@@ -3451,6 +3452,10 @@ int home_resize_luks(
|
||||
r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_reconcile_blob_dirs(new_home, setup->root_fd, reconciled);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
@@ -3539,6 +3544,10 @@ int home_resize_luks(
|
||||
r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_reconcile_blob_dirs(new_home, setup->root_fd, reconciled);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+84
-14
@@ -11,6 +11,7 @@
|
||||
#include "filesystems.h"
|
||||
#include "fs-util.h"
|
||||
#include "home-util.h"
|
||||
#include "homework-blob.h"
|
||||
#include "homework-cifs.h"
|
||||
#include "homework-directory.h"
|
||||
#include "homework-fido2.h"
|
||||
@@ -24,6 +25,7 @@
|
||||
#include "memory-util.h"
|
||||
#include "missing_magic.h"
|
||||
#include "mount-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "recovery-key.h"
|
||||
#include "rm-rf.h"
|
||||
@@ -695,7 +697,7 @@ int home_load_embedded_identity(
|
||||
if (ret_new_home)
|
||||
*ret_new_home = TAKE_PTR(new_home);
|
||||
|
||||
return 0;
|
||||
return r; /* We pass along who won the reconciliation */
|
||||
}
|
||||
|
||||
int home_store_embedded_identity(UserRecord *h, int root_fd, UserRecord *old_home) {
|
||||
@@ -826,7 +828,7 @@ int home_refresh(
|
||||
UserRecord **ret_new_home) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *new_home = NULL;
|
||||
int r;
|
||||
int r, reconciled;
|
||||
|
||||
assert(h);
|
||||
assert(setup);
|
||||
@@ -835,9 +837,9 @@ int home_refresh(
|
||||
/* When activating a home directory, does the identity work: loads the identity from the $HOME
|
||||
* directory, reconciles it with our idea, chown()s everything. */
|
||||
|
||||
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, cache, &embedded_home, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
reconciled = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, cache, &embedded_home, &new_home);
|
||||
if (reconciled < 0)
|
||||
return reconciled;
|
||||
|
||||
r = home_maybe_shift_uid(h, flags, setup);
|
||||
if (r < 0)
|
||||
@@ -851,6 +853,10 @@ int home_refresh(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_reconcile_blob_dirs(new_home, setup->root_fd, reconciled);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = chown_recursive_directory(setup->root_fd, h->uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1071,6 +1077,10 @@ int home_populate(UserRecord *h, int dir_fd) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_reconcile_blob_dirs(h, dir_fd, USER_RECONCILE_HOST_WON);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = chown_recursive_directory(dir_fd, h->uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1305,7 +1315,7 @@ static int determine_default_storage(UserStorage *ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int home_create(UserRecord *h, UserRecord **ret_home) {
|
||||
static int home_create(UserRecord *h, Hashmap *blobs, UserRecord **ret_home) {
|
||||
_cleanup_strv_free_erase_ char **effective_passwords = NULL;
|
||||
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
|
||||
@@ -1367,6 +1377,10 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
|
||||
if (!IN_SET(r, USER_TEST_ABSENT, USER_TEST_UNDEFINED, USER_TEST_MAYBE))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Image path %s already exists, refusing.", user_record_image_path(h));
|
||||
|
||||
r = home_apply_new_blob_dir(h, blobs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
switch (user_record_storage(h)) {
|
||||
|
||||
case USER_LUKS:
|
||||
@@ -1572,7 +1586,7 @@ static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags
|
||||
return has_mount; /* return true if the home record is already active */
|
||||
}
|
||||
|
||||
static int home_update(UserRecord *h, UserRecord **ret) {
|
||||
static int home_update(UserRecord *h, Hashmap *blobs, UserRecord **ret) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL, *embedded_home = NULL;
|
||||
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
|
||||
_cleanup_(password_cache_free) PasswordCache cache = {};
|
||||
@@ -1591,6 +1605,10 @@ static int home_update(UserRecord *h, UserRecord **ret) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_apply_new_blob_dir(h, blobs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_setup(h, flags, &setup, &cache, &header_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1611,6 +1629,10 @@ static int home_update(UserRecord *h, UserRecord **ret) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_reconcile_blob_dirs(new_home, setup.root_fd, USER_RECONCILE_HOST_WON);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_extend_embedded_identity(new_home, h, &setup);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1682,7 +1704,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
|
||||
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
|
||||
_cleanup_(password_cache_free) PasswordCache cache = {};
|
||||
HomeSetupFlags flags = 0;
|
||||
int r;
|
||||
int r, reconciled;
|
||||
|
||||
assert(h);
|
||||
assert(ret_home);
|
||||
@@ -1702,9 +1724,9 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &cache, &embedded_home, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
reconciled = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &cache, &embedded_home, &new_home);
|
||||
if (reconciled < 0)
|
||||
return reconciled;
|
||||
|
||||
r = home_maybe_shift_uid(h, flags, &setup);
|
||||
if (r < 0)
|
||||
@@ -1736,6 +1758,10 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_reconcile_blob_dirs(new_home, setup.root_fd, reconciled);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_extend_embedded_identity(new_home, h, &setup);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1850,10 +1876,12 @@ static int run(int argc, char *argv[]) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *home = NULL, *new_home = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_fclose_ FILE *opened_file = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
||||
unsigned line = 0, column = 0;
|
||||
const char *json_path = NULL;
|
||||
const char *json_path = NULL, *blob_filename;
|
||||
FILE *json_file;
|
||||
usec_t start;
|
||||
JsonVariant *fdmap, *blob_fd_variant;
|
||||
int r;
|
||||
|
||||
start = now(CLOCK_MONOTONIC);
|
||||
@@ -1884,6 +1912,48 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON data: %m", json_path, line, column);
|
||||
|
||||
fdmap = json_variant_by_key(v, HOMEWORK_BLOB_FDMAP_FIELD);
|
||||
if (fdmap) {
|
||||
r = hashmap_ensure_allocated(&blobs, &blob_fd_hash_ops);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
JSON_VARIANT_OBJECT_FOREACH(blob_filename, blob_fd_variant, fdmap) {
|
||||
_cleanup_free_ char *filename = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
|
||||
assert(json_variant_is_integer(blob_fd_variant));
|
||||
assert(json_variant_integer(blob_fd_variant) >= 0);
|
||||
assert(json_variant_integer(blob_fd_variant) <= INT_MAX - SD_LISTEN_FDS_START);
|
||||
fd = SD_LISTEN_FDS_START + (int) json_variant_integer(blob_fd_variant);
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
r = fd_get_path(fd, &resolved);
|
||||
log_debug("Got blob from daemon: %s (%d) → %s",
|
||||
blob_filename, fd, resolved ?: STRERROR(r));
|
||||
}
|
||||
|
||||
filename = strdup(blob_filename);
|
||||
if (!filename)
|
||||
return log_oom();
|
||||
|
||||
r = fd_cloexec(fd, true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enable O_CLOEXEC on blob %s: %m", filename);
|
||||
|
||||
r = hashmap_put(blobs, filename, FD_TO_PTR(fd));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to insert blob %s into map: %m", filename);
|
||||
TAKE_PTR(filename); /* Ownership transfers to hashmap */
|
||||
TAKE_FD(fd);
|
||||
}
|
||||
|
||||
r = json_variant_filter(&v, STRV_MAKE(HOMEWORK_BLOB_FDMAP_FIELD));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to strip internal fdmap from JSON: %m");
|
||||
}
|
||||
|
||||
home = user_record_new();
|
||||
if (!home)
|
||||
return log_oom();
|
||||
@@ -1927,11 +1997,11 @@ static int run(int argc, char *argv[]) {
|
||||
else if (streq(argv[1], "deactivate-force"))
|
||||
r = home_deactivate(home, true);
|
||||
else if (streq(argv[1], "create"))
|
||||
r = home_create(home, &new_home);
|
||||
r = home_create(home, blobs, &new_home);
|
||||
else if (streq(argv[1], "remove"))
|
||||
r = home_remove(home);
|
||||
else if (streq(argv[1], "update"))
|
||||
r = home_update(home, &new_home);
|
||||
r = home_update(home, blobs, &new_home);
|
||||
else if (streq(argv[1], "resize")) /* Resize on user request */
|
||||
r = home_resize(home, false, &new_home);
|
||||
else if (streq(argv[1], "resize-auto")) /* Automatic resize */
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
systemd_homework_sources = files(
|
||||
'home-util.c',
|
||||
'homework-blob.c',
|
||||
'homework-cifs.c',
|
||||
'homework-directory.c',
|
||||
'homework-fscrypt.c',
|
||||
|
||||
@@ -77,6 +77,10 @@
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="CreateHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="CreateHomeEx"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="RealizeHome"/>
|
||||
@@ -97,6 +101,10 @@
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="UpdateHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="UpdateHomeEx"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="ResizeHome"/>
|
||||
@@ -183,6 +191,10 @@
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Update"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="UpdateEx"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Resize"/>
|
||||
|
||||
+119
-1
@@ -3,6 +3,7 @@
|
||||
#include <sys/xattr.h>
|
||||
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "home-util.h"
|
||||
#include "id128-util.h"
|
||||
#include "libcrypt-util.h"
|
||||
@@ -10,6 +11,7 @@
|
||||
#include "recovery-key.h"
|
||||
#include "mountpoint-util.h"
|
||||
#include "path-util.h"
|
||||
#include "sha256.h"
|
||||
#include "stat-util.h"
|
||||
#include "user-record-util.h"
|
||||
#include "user-util.h"
|
||||
@@ -282,7 +284,7 @@ int user_record_add_binding(
|
||||
gid_t gid) {
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *new_binding_entry = NULL, *binding = NULL;
|
||||
_cleanup_free_ char *ip = NULL, *hd = NULL, *ip_auto = NULL, *lc = NULL, *lcm = NULL, *fst = NULL;
|
||||
_cleanup_free_ char *blob = NULL, *ip = NULL, *hd = NULL, *ip_auto = NULL, *lc = NULL, *lcm = NULL, *fst = NULL;
|
||||
sd_id128_t mid;
|
||||
int r;
|
||||
|
||||
@@ -291,6 +293,10 @@ int user_record_add_binding(
|
||||
if (!h->json)
|
||||
return -EUNATCH;
|
||||
|
||||
blob = path_join(home_system_blob_dir(), h->user_name);
|
||||
if (!blob)
|
||||
return -ENOMEM;
|
||||
|
||||
r = sd_id128_get_machine(&mid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -331,6 +337,7 @@ int user_record_add_binding(
|
||||
|
||||
r = json_build(&new_binding_entry,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("blobDirectory", JSON_BUILD_STRING(blob)),
|
||||
JSON_BUILD_PAIR_CONDITION(!!image_path, "imagePath", JSON_BUILD_STRING(image_path)),
|
||||
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(partition_uuid), "partitionUuid", JSON_BUILD_STRING(SD_ID128_TO_UUID_STRING(partition_uuid))),
|
||||
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(luks_uuid), "luksUuid", JSON_BUILD_STRING(SD_ID128_TO_UUID_STRING(luks_uuid))),
|
||||
@@ -370,6 +377,8 @@ int user_record_add_binding(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
free_and_replace(h->blob_directory, blob);
|
||||
|
||||
if (storage >= 0)
|
||||
h->storage = storage;
|
||||
|
||||
@@ -1383,6 +1392,15 @@ int user_record_is_supported(UserRecord *hr, sd_bus_error *error) {
|
||||
if (hr->service && !streq(hr->service, "io.systemd.Home"))
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Not accepted with service not matching io.systemd.Home.");
|
||||
|
||||
if (hr->blob_directory) {
|
||||
/* This function is always called w/o binding section, so if hr->blob_dir is set then the caller set it themselves */
|
||||
assert((hr->mask & USER_RECORD_BINDING) == 0);
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot manage custom blob directories.");
|
||||
}
|
||||
|
||||
if (json_variant_by_key(hr->json, HOMEWORK_BLOB_FDMAP_FIELD))
|
||||
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "User record contains unsafe internal fields.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1511,3 +1529,103 @@ int user_record_set_rebalance_weight(UserRecord *h, uint64_t weight) {
|
||||
h->mask |= USER_RECORD_PER_MACHINE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int user_record_ensure_blob_manifest(UserRecord *h, Hashmap *blobs, const char **ret_failed) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *manifest = NULL;
|
||||
const char *filename;
|
||||
void *key, *value;
|
||||
uint64_t total_size = 0;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(h->json);
|
||||
assert(blobs);
|
||||
assert(ret_failed);
|
||||
|
||||
/* Ensures that blobManifest exists (possibly creating it using the
|
||||
* contents of blobs), and that the set of keys in both hashmaps are
|
||||
* exactly the same. If it fails to handle one blob file, the filename
|
||||
* is put it ret_failed for nicer error reporting. ret_failed is a pointer
|
||||
* to the same memory blobs uses to store its keys, so it is valid for
|
||||
* as long as blobs is valid and the corresponding key isn't removed! */
|
||||
|
||||
if (h->blob_manifest) {
|
||||
/* blobManifest already exists. In this case we verify
|
||||
* that the sets of keys are equal and that's it */
|
||||
|
||||
HASHMAP_FOREACH_KEY(value, key, h->blob_manifest)
|
||||
if (!hashmap_contains(blobs, key))
|
||||
return -EINVAL;
|
||||
HASHMAP_FOREACH_KEY(value, key, blobs)
|
||||
if (!hashmap_contains(h->blob_manifest, key))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* blobManifest doesn't exist, so we need to create it */
|
||||
|
||||
HASHMAP_FOREACH_KEY(value, filename, blobs) {
|
||||
_cleanup_free_ char *filename_dup = NULL;
|
||||
_cleanup_free_ uint8_t *hash = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *hash_json = NULL;
|
||||
int fd = PTR_TO_FD(value);
|
||||
off_t initial, size;
|
||||
|
||||
*ret_failed = filename;
|
||||
|
||||
filename_dup = strdup(filename);
|
||||
if (!filename_dup)
|
||||
return -ENOMEM;
|
||||
|
||||
hash = malloc(SHA256_DIGEST_SIZE);
|
||||
if (!hash)
|
||||
return -ENOMEM;
|
||||
|
||||
initial = lseek(fd, 0, SEEK_CUR);
|
||||
if (initial < 0)
|
||||
return -errno;
|
||||
|
||||
r = sha256_fd(fd, BLOB_DIR_MAX_SIZE, hash);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
size = lseek(fd, 0, SEEK_CUR);
|
||||
if (size < 0)
|
||||
return -errno;
|
||||
if (!DEC_SAFE(&size, initial))
|
||||
return -EOVERFLOW;
|
||||
|
||||
if (!INC_SAFE(&total_size, size))
|
||||
total_size = UINT64_MAX;
|
||||
if (total_size > BLOB_DIR_MAX_SIZE)
|
||||
return -EFBIG;
|
||||
|
||||
if (lseek(fd, initial, SEEK_SET) < 0)
|
||||
return -errno;
|
||||
|
||||
r = json_variant_new_hex(&hash_json, hash, SHA256_DIGEST_SIZE);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename_dup, hash);
|
||||
if (r < 0)
|
||||
return r;
|
||||
TAKE_PTR(filename_dup); /* Ownership transfers to hashmap */
|
||||
TAKE_PTR(hash);
|
||||
|
||||
r = json_variant_set_field(&v, filename, hash_json);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret_failed = NULL;
|
||||
}
|
||||
|
||||
r = json_variant_set_field_non_null(&h->json, "blobManifest", v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
h->blob_manifest = TAKE_PTR(manifest);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
#include "user-record.h"
|
||||
#include "group-record.h"
|
||||
|
||||
/* We intentionally use snake_case instead of the usual camelCase here to further
|
||||
* reduce the chance of collision with a field any legitimate user record may ever
|
||||
* want to set. */
|
||||
#define HOMEWORK_BLOB_FDMAP_FIELD "__systemd_homework_internal_blob_fdmap"
|
||||
|
||||
int user_record_synthesize(UserRecord *h, const char *user_name, const char *realm, const char *image_path, UserStorage storage, uid_t uid, gid_t gid);
|
||||
int group_record_synthesize(GroupRecord *g, UserRecord *u);
|
||||
|
||||
@@ -63,3 +68,5 @@ int user_record_is_supported(UserRecord *hr, sd_bus_error *error);
|
||||
|
||||
bool user_record_shall_rebalance(UserRecord *h);
|
||||
int user_record_set_rebalance_weight(UserRecord *h, uint64_t weight);
|
||||
|
||||
int user_record_ensure_blob_manifest(UserRecord *h, Hashmap *blobs, const char **ret_failed);
|
||||
|
||||
@@ -539,7 +539,7 @@ int fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret
|
||||
|
||||
r = safe_fork_full(name,
|
||||
NULL,
|
||||
except,
|
||||
(int*) except, /* safe_fork_full only changes except if you pass in FORK_PACK_FDS, which we don't */
|
||||
n_except,
|
||||
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE,
|
||||
ret_pid);
|
||||
|
||||
@@ -3,8 +3,14 @@
|
||||
#include "cap-list.h"
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "glyph-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "process-util.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "sha256.h"
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
#include "user-record-show.h"
|
||||
@@ -213,6 +219,37 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (hr->blob_directory) {
|
||||
_cleanup_free_ char **filenames = NULL;
|
||||
size_t n_filenames = 0;
|
||||
|
||||
r = hashmap_dump_keys_sorted(hr->blob_manifest, (void***) &filenames, &n_filenames);
|
||||
if (r < 0) {
|
||||
errno = -r;
|
||||
printf(" Blob Dir.: %s (can't iterate: %m)\n", hr->blob_directory);
|
||||
} else
|
||||
printf(" Blob Dir.: %s\n", hr->blob_directory);
|
||||
|
||||
for (size_t i = 0; i < n_filenames; i++) {
|
||||
_cleanup_free_ char *path = NULL, *link = NULL, *hash = NULL;
|
||||
const char *filename = filenames[i];
|
||||
const uint8_t *hash_bytes = hashmap_get(hr->blob_manifest, filename);
|
||||
bool last = i == n_filenames - 1;
|
||||
|
||||
path = path_join(hr->blob_directory, filename);
|
||||
if (path)
|
||||
(void) terminal_urlify_path(path, filename, &link);
|
||||
hash = hexmem(hash_bytes, SHA256_DIGEST_SIZE);
|
||||
|
||||
printf(" %s %s %s(%s)%s\n",
|
||||
special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH),
|
||||
link ?: filename,
|
||||
ansi_grey(),
|
||||
hash ?: "can't display hash",
|
||||
ansi_normal());
|
||||
}
|
||||
}
|
||||
|
||||
storage = user_record_storage(hr);
|
||||
if (storage >= 0) /* Let's be political, and clarify which storage we like, and which we don't. About CIFS we don't complain. */
|
||||
printf(" Storage: %s%s\n", user_storage_to_string(storage),
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
#include "path-util.h"
|
||||
#include "pkcs11-util.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "sha256.h"
|
||||
#include "string-table.h"
|
||||
#include "strv.h"
|
||||
#include "uid-classification.h"
|
||||
#include "user-record.h"
|
||||
#include "user-util.h"
|
||||
#include "utf8.h"
|
||||
|
||||
#define DEFAULT_RATELIMIT_BURST 30
|
||||
#define DEFAULT_RATELIMIT_INTERVAL_USEC (1*USEC_PER_MINUTE)
|
||||
@@ -142,6 +144,9 @@ static UserRecord* user_record_free(UserRecord *h) {
|
||||
free(h->location);
|
||||
free(h->icon_name);
|
||||
|
||||
free(h->blob_directory);
|
||||
hashmap_free(h->blob_manifest);
|
||||
|
||||
free(h->shell);
|
||||
|
||||
strv_free(h->environment);
|
||||
@@ -1074,6 +1079,7 @@ static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispa
|
||||
static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch binding_dispatch_table[] = {
|
||||
{ "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
|
||||
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_image_path, offsetof(UserRecord, image_path), 0 },
|
||||
{ "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
|
||||
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
|
||||
@@ -1110,6 +1116,52 @@ static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatch
|
||||
return json_dispatch(m, binding_dispatch_table, flags, userdata);
|
||||
}
|
||||
|
||||
static int dispatch_blob_manifest(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
_cleanup_hashmap_free_ Hashmap *manifest = NULL;
|
||||
Hashmap **ret = ASSERT_PTR(userdata);
|
||||
JsonVariant *value;
|
||||
const char *key;
|
||||
int r;
|
||||
|
||||
if (!variant)
|
||||
return 0;
|
||||
|
||||
if (!json_variant_is_object(variant))
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
|
||||
|
||||
JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
|
||||
_cleanup_free_ char *filename = NULL;
|
||||
_cleanup_free_ uint8_t *hash = NULL;
|
||||
|
||||
if (!json_variant_is_string(value))
|
||||
return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid hash.", key);
|
||||
|
||||
if (!suitable_blob_filename(key))
|
||||
return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid filename.", key);
|
||||
|
||||
filename = strdup(key);
|
||||
if (!filename)
|
||||
return json_log_oom(value, flags);
|
||||
|
||||
hash = malloc(SHA256_DIGEST_SIZE);
|
||||
if (!hash)
|
||||
return json_log_oom(value, flags);
|
||||
|
||||
r = parse_sha256(json_variant_string(value), hash);
|
||||
if (r < 0)
|
||||
return json_log(value, flags, r, "Blob entry '%s' has invalid hash: %s", filename, json_variant_string(value));
|
||||
|
||||
r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename, hash);
|
||||
if (r < 0)
|
||||
return json_log(value, flags, r, "Failed to insert blob manifest entry '%s': %m", filename);
|
||||
TAKE_PTR(filename); /* Ownership transfers to hashmap */
|
||||
TAKE_PTR(hash);
|
||||
}
|
||||
|
||||
hashmap_free_and_replace(*ret, manifest);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags) {
|
||||
sd_id128_t mid;
|
||||
int r;
|
||||
@@ -1226,6 +1278,8 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
|
||||
static const JsonDispatch per_machine_dispatch_table[] = {
|
||||
{ "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
|
||||
{ "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
|
||||
{ "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
|
||||
{ "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 },
|
||||
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
|
||||
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
|
||||
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
|
||||
@@ -1560,6 +1614,8 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
|
||||
static const JsonDispatch user_dispatch_table[] = {
|
||||
{ "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), JSON_RELAX},
|
||||
{ "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 },
|
||||
{ "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
|
||||
{ "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 },
|
||||
{ "realName", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(UserRecord, real_name), 0 },
|
||||
{ "emailAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, email_address), JSON_SAFE },
|
||||
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
|
||||
@@ -2373,6 +2429,13 @@ int user_record_test_password_change_required(UserRecord *h) {
|
||||
return change_permitted ? 0 : -EROFS;
|
||||
}
|
||||
|
||||
int suitable_blob_filename(const char *name) {
|
||||
/* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */
|
||||
return filename_is_valid(name) &&
|
||||
in_charset(name, URI_UNRESERVED) &&
|
||||
name[0] != '.';
|
||||
}
|
||||
|
||||
static const char* const user_storage_table[_USER_STORAGE_MAX] = {
|
||||
[USER_CLASSIC] = "classic",
|
||||
[USER_LUKS] = "luks",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "json.h"
|
||||
#include "missing_resource.h"
|
||||
#include "time-util.h"
|
||||
@@ -243,6 +244,9 @@ typedef struct UserRecord {
|
||||
char *icon_name;
|
||||
char *location;
|
||||
|
||||
char *blob_directory;
|
||||
Hashmap *blob_manifest;
|
||||
|
||||
UserDisposition disposition;
|
||||
uint64_t last_change_usec;
|
||||
uint64_t last_password_change_usec;
|
||||
@@ -449,6 +453,9 @@ int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags);
|
||||
int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags);
|
||||
int user_group_record_mangle(JsonVariant *v, UserRecordLoadFlags load_flags, JsonVariant **ret_variant, UserRecordMask *ret_mask);
|
||||
|
||||
#define BLOB_DIR_MAX_SIZE (UINT64_C(64) * U64_MB)
|
||||
int suitable_blob_filename(const char *name);
|
||||
|
||||
const char* user_storage_to_string(UserStorage t) _const_;
|
||||
UserStorage user_storage_from_string(const char *s) _pure_;
|
||||
|
||||
|
||||
+134
-3
@@ -156,9 +156,7 @@ PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
|
||||
(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz)
|
||||
PASSWORD=xEhErW0ndafV4s homectl with test-user -- touch /home/test-user/xyz
|
||||
PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz
|
||||
PASSWORD=xEhErW0ndafV4s homectl with test-user -- rm /home/test-user/xyz
|
||||
PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
|
||||
(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz)
|
||||
# CAREFUL adding more `homectl with` tests here. Auth can get rate-limited and cause the tests to fail.
|
||||
|
||||
wait_for_state test-user inactive
|
||||
homectl remove test-user
|
||||
@@ -170,6 +168,139 @@ if ! systemd-detect-virt -cq ; then
|
||||
homectl remove test-user2
|
||||
fi
|
||||
|
||||
# blob directory tests
|
||||
# See docs/USER_RECORD_BLOB_DIRS.md
|
||||
checkblob() {
|
||||
test -f "/var/cache/systemd/home/blob-user/$1"
|
||||
stat -c "%u %#a" "/var/cache/systemd/home/blob-user/$1" | grep "^0 0644"
|
||||
test -f "/home/blob-user/.identity-blob/$1"
|
||||
stat -c "%u %#a" "/home/blob-user/.identity-blob/$1" | grep "^12345 0644"
|
||||
|
||||
diff "/var/cache/systemd/home/blob-user/$1" "$2"
|
||||
diff "/var/cache/systemd/home/blob-user/$1" "/home/blob-user/.identity-blob/$1"
|
||||
}
|
||||
|
||||
mkdir /tmp/blob1 /tmp/blob2
|
||||
echo data1 blob1 > /tmp/blob1/test1
|
||||
echo data1 blob2 > /tmp/blob2/test1
|
||||
echo data2 blob1 > /tmp/blob1/test2
|
||||
echo data2 blob2 > /tmp/blob2/test2
|
||||
echo invalid filename > /tmp/blob1/файл
|
||||
echo data3 > /tmp/external-test3
|
||||
echo avatardata > /tmp/external-avatar
|
||||
ln -s /tmp/external-avatar /tmp/external-avatar-lnk
|
||||
dd if=/dev/urandom of=/tmp/external-barely-fits bs=1M count=64
|
||||
dd if=/dev/urandom of=/tmp/external-toobig bs=1M count=65
|
||||
|
||||
# create w/ prepopulated blob dir
|
||||
NEWPASSWORD=EMJuc3zQaMibJo homectl create blob-user \
|
||||
--disk-size=min --luks-discard=yes \
|
||||
--luks-pbkdf-type=pbkdf2 --luks-pbkdf-time-cost=1ms \
|
||||
--uid=12345 \
|
||||
--blob=/tmp/blob1
|
||||
inspect blob-user
|
||||
PASSWORD=EMJuc3zQaMibJo homectl activate blob-user
|
||||
inspect blob-user
|
||||
|
||||
test -d /var/cache/systemd/home/blob-user
|
||||
stat -c "%u %#a" /var/cache/systemd/home/blob-user | grep "^0 0755"
|
||||
test -d /home/blob-user/.identity-blob
|
||||
stat -c "%u %#a" /home/blob-user/.identity-blob | grep "^12345 0700"
|
||||
|
||||
checkblob test1 /tmp/blob1/test1
|
||||
(! checkblob test1 /tmp/blob2/test1 )
|
||||
checkblob test2 /tmp/blob1/test2
|
||||
(! checkblob test2 /tmp/blob2/test2 )
|
||||
(! checkblob фаил /tmp/blob1/фаил )
|
||||
(! checkblob test3 /tmp/external-test3 )
|
||||
(! checkblob avatar /tmp/external-avatar )
|
||||
|
||||
# append files to existing blob, both well-known and other
|
||||
PASSWORD=EMJuc3zQaMibJo homectl update blob-user \
|
||||
-b test3=/tmp/external-test3 --avatar=/tmp/external-avatar
|
||||
inspect blob-user
|
||||
checkblob test1 /tmp/blob1/test1
|
||||
(! checkblob test1 /tmp/blob2/test1 )
|
||||
checkblob test2 /tmp/blob1/test2
|
||||
(! checkblob test2 /tmp/blob2/test2 )
|
||||
(! checkblob фаил /tmp/blob1/фаил )
|
||||
checkblob test3 /tmp/external-test3
|
||||
checkblob avatar /tmp/external-avatar
|
||||
|
||||
# delete files from existing blob, both well-known and other
|
||||
PASSWORD=EMJuc3zQaMibJo homectl update blob-user \
|
||||
-b test3= --avatar=
|
||||
inspect blob-user
|
||||
checkblob test1 /tmp/blob1/test1
|
||||
(! checkblob test1 /tmp/blob2/test1 )
|
||||
checkblob test2 /tmp/blob1/test2
|
||||
(! checkblob test2 /tmp/blob2/test2 )
|
||||
(! checkblob фаил /tmp/blob1/фаил )
|
||||
(! checkblob test3 /tmp/external-test3 )
|
||||
(! checkblob avatar /tmp/external-avatar )
|
||||
|
||||
# swap entire blob directory
|
||||
PASSWORD=EMJuc3zQaMibJo homectl update blob-user \
|
||||
-b /tmp/blob2
|
||||
inspect blob-user
|
||||
(! checkblob test1 /tmp/blob1/test1 )
|
||||
checkblob test1 /tmp/blob2/test1
|
||||
(! checkblob test2 /tmp/blob1/test2 )
|
||||
checkblob test2 /tmp/blob2/test2
|
||||
(! checkblob фаил /tmp/blob1/фаил )
|
||||
(! checkblob test3 /tmp/external-test3 )
|
||||
(! checkblob avatar /tmp/external-avatar )
|
||||
|
||||
# create and delete files while swapping blob directory. Also symlinks.
|
||||
PASSWORD=EMJuc3zQaMibJo homectl update blob-user \
|
||||
-b /tmp/blob1 -b test2= -b test3=/tmp/external-test3 --avatar=/tmp/external-avatar-lnk
|
||||
inspect blob-user
|
||||
checkblob test1 /tmp/blob1/test1
|
||||
(! checkblob test1 /tmp/blob2/test1 )
|
||||
(! checkblob test2 /tmp/blob1/test2 )
|
||||
(! checkblob test2 /tmp/blob2/test2 )
|
||||
(! checkblob фаил /tmp/blob1/фаил )
|
||||
checkblob test3 /tmp/external-test3
|
||||
checkblob avatar /tmp/external-avatar # target of the link
|
||||
|
||||
# clear the blob directory
|
||||
PASSWORD=EMJuc3zQaMibJo homectl update blob-user \
|
||||
-b /tmp/blob2 -b test3=/tmp/external-test3 --blob=
|
||||
inspect blob-user
|
||||
(! checkblob test1 /tmp/blob1/test1 )
|
||||
(! checkblob test1 /tmp/blob2/test1 )
|
||||
(! checkblob test2 /tmp/blob1/test2 )
|
||||
(! checkblob test2 /tmp/blob2/test2 )
|
||||
(! checkblob фаил /tmp/blob1/фаил )
|
||||
(! checkblob test3 /tmp/external-test3 )
|
||||
(! checkblob avatar /tmp/external-avatar )
|
||||
|
||||
# file that's exactly 64M still fits
|
||||
PASSWORD=EMJuc3zQaMibJo homectl update blob-user \
|
||||
-b barely-fits=/tmp/external-barely-fits
|
||||
(! checkblob test1 /tmp/blob1/test1 )
|
||||
(! checkblob test1 /tmp/blob2/test1 )
|
||||
(! checkblob test2 /tmp/blob1/test2 )
|
||||
(! checkblob test2 /tmp/blob2/test2 )
|
||||
(! checkblob фаил /tmp/blob1/фаил )
|
||||
(! checkblob test3 /tmp/external-test3 )
|
||||
(! checkblob avatar /tmp/external-avatar )
|
||||
checkblob barely-fits /tmp/external-barely-fits
|
||||
|
||||
# error out if the file is too big
|
||||
(! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b huge=/tmp/external-toobig )
|
||||
|
||||
# error out if filenames are invalid
|
||||
(! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b .hidden=/tmp/external-test3 )
|
||||
(! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b "with spaces=/tmp/external-test3" )
|
||||
(! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b with=equals=/tmp/external-test3 )
|
||||
(! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b файл=/tmp/external-test3 )
|
||||
(! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b special@chars=/tmp/external-test3 )
|
||||
|
||||
homectl deactivate blob-user
|
||||
wait_for_state blob-user inactive
|
||||
homectl remove blob-user
|
||||
|
||||
# userdbctl tests
|
||||
export PAGER=
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_ALG AF_INET AF_INET6
|
||||
RestrictNamespaces=mnt user
|
||||
RestrictRealtime=yes
|
||||
StateDirectory=systemd/home
|
||||
CacheDirectory=systemd/home
|
||||
SystemCallArchitectures=native
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallFilter=@system-service @mount quotactl
|
||||
|
||||
Reference in New Issue
Block a user