Compare commits
15 Commits
70e9d9a56c
...
4119d1e60a
Author | SHA1 | Date |
---|---|---|
Zbigniew Jędrzejewski-Szmek | 4119d1e60a | |
Piotr Drąg | ce306dd872 | |
Lennart Poettering | c809ed783e | |
Lennart Poettering | 0edd431e15 | |
Lennart Poettering | a9dabd6866 | |
Lennart Poettering | f62dd2375e | |
Lennart Poettering | 28e208a7d8 | |
Lennart Poettering | 38e7b808eb | |
Lennart Poettering | ea7a19e95d | |
Lennart Poettering | ba0fb5acd4 | |
Lennart Poettering | 6ead39170a | |
Lennart Poettering | 26cf9fb7f8 | |
Lennart Poettering | 4aa0a8ac3e | |
Lennart Poettering | 70a5db5822 | |
Lennart Poettering | e53db1405c |
|
@ -5,6 +5,7 @@ extraction:
|
|||
- python3-pip
|
||||
- python3-setuptools
|
||||
- python3-wheel
|
||||
- libpwquality-dev
|
||||
- libfdisk-dev
|
||||
- libp11-kit-dev
|
||||
- libssl-dev
|
||||
|
|
|
@ -43,6 +43,7 @@ BuildPackages=
|
|||
libidn2-devel
|
||||
libmicrohttpd-devel
|
||||
libmount-devel
|
||||
libpwquality-devel
|
||||
libseccomp-devel
|
||||
libselinux-devel
|
||||
libxkbcommon-devel
|
||||
|
@ -52,6 +53,7 @@ BuildPackages=
|
|||
m4
|
||||
meson
|
||||
openssl-devel
|
||||
p11-kit-devel
|
||||
pam-devel
|
||||
pcre2-devel
|
||||
pkgconfig
|
||||
|
@ -66,6 +68,7 @@ Packages=
|
|||
coreutils
|
||||
cryptsetup-libs
|
||||
kmod-libs
|
||||
e2fsprogs
|
||||
libidn2
|
||||
libseccomp
|
||||
procps-ng
|
||||
|
|
44
TODO
44
TODO
|
@ -43,6 +43,9 @@ Features:
|
|||
* honour specifiers in unit files that resolve to some very basic
|
||||
/etc/os-release data, such as ID, VERSION_ID, BUILD_ID, VARIANT_ID.
|
||||
|
||||
* cryptsetup: allow encoding key directly in /etc/crypttab, maybe with a
|
||||
"base64:" prefix. Useful in particular for pkcs11 mode.
|
||||
|
||||
* socket units: allow creating a udev monitor socket with ListenDevices= or so,
|
||||
with matches, then actviate app thorugh that passing socket oveer
|
||||
|
||||
|
@ -195,6 +198,38 @@ Features:
|
|||
user@.service, which returns the XDG_RUNTIME_DIR value, and make this
|
||||
behaviour selectable via pam module option.
|
||||
|
||||
* homed:
|
||||
- when user tries to log into record signed by unrecognized key, automatically add key to our chain after polkit auth
|
||||
- hook up machined/nspawn users with a varlink user query interface
|
||||
- rollback when resize fails mid-operation
|
||||
- GNOME's side for forget key on suspend (requires rework so that lock screen runs outside of uid)
|
||||
- resize on login?
|
||||
- fstrim on logout?
|
||||
- shrink fs on logout?
|
||||
- update LUKS password on login if we find there's a password that unlocks the JSON record but not the LUKS device.
|
||||
- create on activate?
|
||||
- properties: icon url?, preferred session type?, administrator bool (which translates to 'wheel' membership)?, address?, telephone?, vcard?, samba stuff?, parental controls?
|
||||
- communicate clearly when usb stick is safe to remove. probably involves
|
||||
beefing up logind to make pam session close hook synchronous and wait until
|
||||
systemd --user is shut down.
|
||||
- logind: maybe keep a "busy fd" as long as there's a non-released session around or the user@.service
|
||||
- maybe make automatic, read-only, time-based reflink-copies of LUKS disk images (think: time machine)
|
||||
- distuingish destroy / remove (i.e. currently we can unregister a user, unregister+remove their home directory, but not just remove their home directory)
|
||||
- in systemd's PAMName= logic: query passwords with ssh-askpassword, so that we can make "loginctl set-linger" mode work
|
||||
- fingerprint authentication, pattern authentication, …
|
||||
- make sure "classic" user records can also be managed by homed
|
||||
- description field for groups
|
||||
- make size of $XDG_RUNTIME_DIR configurable in user record
|
||||
- reuse pwquality magic in firstboot
|
||||
- query password from kernel keyring first
|
||||
- update even if record is "absent"
|
||||
- add a "access mode" + "fstype" field to the "status" section of json identity records reflecting the actually used access mode and fstype, even on non-luks backends
|
||||
- move acct mgmt stuff from pam_systemd_home to pam_systemd?
|
||||
- when "homectl --pkcs11-token-uri=" is used, synthesize ssh-authorized-keys records for all keys we have private keys on the stick for
|
||||
- make slice for users configurable (requires logind rework)
|
||||
- logind: populate auto-login list bus property from PKCS#11 token
|
||||
- when determining state of a LUKS home directory, check DM suspended sysfs file
|
||||
|
||||
* introduce a new per-process uuid, similar to the boot id, the machine id, the
|
||||
invocation id, that is derived from process creds, specifically a hashed
|
||||
combination of AT_RANDOM + getpid() + the starttime from
|
||||
|
@ -496,15 +531,6 @@ Features:
|
|||
"systemd-gdb" for attaching to the start-up of any system service in its
|
||||
natural habitat.
|
||||
|
||||
* maybe add gpt-partition-based user management: each user gets his own
|
||||
LUKS-encrypted GPT partition with a new GPT type. A small nss module
|
||||
enumerates users via udev partition enumeration. UIDs are assigned in a fixed
|
||||
way: the partition index is added as offset to some fixed base uid. User name
|
||||
is stored in GPT partition name. A PAM module authenticates the user via the
|
||||
LUKS partition password. Benefits: strong per-user security, compatibility
|
||||
with stateless/read-only/verity-enabled root. (other idea: do this based on
|
||||
loopback files in /home, without GPT involvement)
|
||||
|
||||
* gpt-auto logic: related to the above, maybe support a "secondary" root
|
||||
partition, that is mounted to / and is writable, and where the actual root's
|
||||
/usr is mounted into.
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
---
|
||||
title: Home Directories
|
||||
category: Concepts
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Home Directories
|
||||
|
||||
[`systemd-homed.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-homed.service.html)
|
||||
manages home directories of regular ("human") users. Each directory it manages
|
||||
encapsulates both the data store and the user record of the user so that it
|
||||
comprehensively describes the user account, and is thus naturally portable
|
||||
between systems without any further, external metadata. This document describes
|
||||
the format used by these home directories, in context of the storage mechanism
|
||||
used.
|
||||
|
||||
## General Structure
|
||||
|
||||
Inside of the home directory a file `~/.identity` contains the JSON formatted
|
||||
user record of the user. It follows the format defined in [`JSON User
|
||||
Records`](https://systemd.io/USER_RECORDS). It is recommended to bring the
|
||||
record into 'normalized' form (i.e. all objects should contain their fields
|
||||
sorted alphabetically by their key) before storing it there, though this is not
|
||||
required nor enforced. Since the user record is cryptographically signed the
|
||||
user cannot make modifications to the file on their own (at least not without
|
||||
corrupting it, or knowing the private key used for signing the record). Note
|
||||
that user records are stored here without their `binding`, `status` and
|
||||
`secret` sections, i.e. only with the sections included in the signature plus
|
||||
the signature section itself.
|
||||
|
||||
## Storage Mechanism: Plain Directory/`btrfs` Subvolume
|
||||
|
||||
If the plain directory or `btrfs` subvolume storage mechanism of
|
||||
`systemd-homed` is used (i.e. `--storage=directory` or `--storage=subvolume` on
|
||||
the
|
||||
[`homectl(1)`](https://www.freedesktop.org/software/systemd/man/homectl.html)
|
||||
command line) the home directory requires no special set-up besides including
|
||||
the user record in the `~/.identity` file.
|
||||
|
||||
It is recommended to name home directories managed this way by
|
||||
`systemd-homed.service` by the user name, suffixed with `.homedir` (example:
|
||||
`lennart.homedir` for a user `lennart`) but this is not enforced. When the user
|
||||
is logged in the directory is generally mounted to `/home/$USER` (in our
|
||||
example: `/home/lennart`), thus dropping the suffix while the home directory is
|
||||
active. `systemd-homed` will automatically discover home directories named this
|
||||
way in `/home/*.homedir` and synthesize NSS user records for them as they show
|
||||
up.
|
||||
|
||||
## Storage Mechanism: `fscrypt` Directories
|
||||
|
||||
This storage mechanism is mostly identical to the plain directory storage
|
||||
mechanism, except that the home directory is encrypted using `fscrypt`. (Use
|
||||
`--storage=fscrypt` on the `homectl` command line.) Key management is
|
||||
implemented via extended attributes on the directory itself: for each password
|
||||
an extended attribute `trusted.fscrypt_slot0`, `trusted.fscrypt_slot1`,
|
||||
`trusted.fscrypt_slot2`, … is maintained. It's value contains a colon-separated
|
||||
pair of Base64 encoded data fields. The first field contains a salt value, the
|
||||
second field the encrypted volume key. The latter is encrypted using AES256 in
|
||||
counter mode, using a key derived from the password via PBKDF2-HMAC-SHA512
|
||||
together with the salt value. The construction is similar to what LUKS does for
|
||||
`dm-crypt` encrypted volumes. Note that extended attributes are not encrypted
|
||||
by `fscrypt` and hence are suitable for carry the key slots. Moreover, by using
|
||||
extended attributes the slots are directly attached to the directory and an
|
||||
independent sidecar key database is not required.
|
||||
|
||||
## Storage Mechanism: `cifs` Home Directories
|
||||
|
||||
In this storage mechanism the home directory is mounted from a CIFS server and
|
||||
service at login, configured inside the user record. (Use `--storage=cifs` on
|
||||
the `homectl` command line.) The local password of the user is used to log into
|
||||
the CIFS service. The directory share needs to contain the user record in
|
||||
`~/.identity` as well. Note that this means that the user record needs to be
|
||||
registered locally before it can be mounted for the first time, since CIFS
|
||||
domain and server information needs to be known *before* the mount. Note that
|
||||
for all other storage mechanisms it is entirely sufficient if the directories
|
||||
or storage artifacts are placed at the right locations — all information to
|
||||
activate them can be derived automatically from their mere availability.
|
||||
|
||||
## Storage Mechanism: `luks` Home Directories
|
||||
|
||||
This is the most advanced and most secure storage mechanism and consists of a
|
||||
Linux file system inside a LUKS2 volume inside a loopback file (or on removable
|
||||
media). (Use `--storage=luks` on the `homectl` command line.) Specifically:
|
||||
|
||||
* The image contains a GPT partition table. For now it should only contain a
|
||||
single partition, and that partition must have the type UUID
|
||||
`773f91ef-66d4-49b5-bd83-d683bf40ad16`. It's partition label must be the
|
||||
user name.
|
||||
|
||||
* This partition must contain a LUKS2 volume, whose label must be the user
|
||||
name. The LUKS2 volume must contain a LUKS2 token field of type
|
||||
`systemd-homed`. The JSON data of this token must have a `record` field,
|
||||
containing a string with base64-encoded data. This data is the JSON user
|
||||
record, in the same serialization as in `~/.identity`, though encrypted. The
|
||||
JSON data of this token must also have an `iv` field, which contains a
|
||||
base64-encoded binary initialization vector for the encryption. The
|
||||
encryption used is the same as the LUKS2 volume itself uses, unlocked by the
|
||||
same volume key, but based on its own IV.
|
||||
|
||||
* Inside of this LUKS2 volume must be a Linux file system, one of `ext4`,
|
||||
`btrfs` and `xfs`. The file system label must be the user name.
|
||||
|
||||
* This file system should contain a single directory named after the user. This
|
||||
directory will become the home directory of the user when activated. It
|
||||
contains a second copy of the user record in the `~/.identity` file, like in
|
||||
the other storage mechanisms.
|
||||
|
||||
The image file should either reside in a directory `/home/` on the system,
|
||||
named after the user, suffixed with `.home`. When activated the container home
|
||||
directory is mounted to the same path, though with the `.home` suffix dropped —
|
||||
unless a different mount point is defined in the user record. (e.g.: the
|
||||
loopback file `/home/waldo.home` is mounted to `/home/waldo` while activated.)
|
||||
When the image is stored on removable media (such as a USB stick) the image
|
||||
file can be directly `dd`'ed onto it, the format is unchanged. The GPT envelope
|
||||
should ensure the image is properly recognizable as a home directory both when
|
||||
used in a loopback file and on a removable USB stick. (Note that when mounting
|
||||
a home directory from an USB stick it too defaults to a directory in `/home/`,
|
||||
named after the username, with no further suffix.)
|
||||
|
||||
Rationale for the GPT partition table envelope: this way the image is nicely
|
||||
discoverable and recognizable already by partition managers as a home
|
||||
directory. Moreover, when copied onto a USB stick the GPT envelope makes sure
|
||||
the stick is properly recognizable as a portable home directory
|
||||
medium. (Moreover it allows to embed additional partitions later on, for
|
||||
example for allowing a multi-purpose USB stick that contains both a home
|
||||
directory and a generic storage volume.)
|
||||
|
||||
Rationale for including the encrypted user record in the the LUKS2 header:
|
||||
Linux kernel file system implementations are generally not robust towards
|
||||
maliciously formatted file systems; there's a good chance that file system
|
||||
images can be used as attack vectors, exploiting the kernel. Thus it is
|
||||
necessary to validate the home directory image *before* mounting it and
|
||||
establishing a minimal level of trust. Since the user record data is
|
||||
cryptographically signed and user records not signed with a recognized private
|
||||
key are not accepted a minimal level of trust between the system and the home
|
||||
directory image is established.
|
||||
|
||||
Rationale for storing the home directory one level below to root directory of
|
||||
the contained file system: this way special directories such as `lost+found/`
|
||||
do not show up in the user's home directory.
|
||||
|
||||
## Algorithm
|
||||
|
||||
Regardless of the storage mechanism used, an activated home directory
|
||||
necessarily involves a mount point to be established. In case of the
|
||||
directory-based storage mechanisms (`directory`, `subvolume` and `fscrypt`)
|
||||
this is a bind mount, in case of `cifs` this is a CIFS network mount, and in
|
||||
case of the LUKS2 backend a regular block device mount of the file system
|
||||
contained in the LUKS2 image. By requiring a mount for all cases (even for
|
||||
those that already are a directory) a clear logic is defined to distuingish
|
||||
active and inactive home directories, so that the directories become
|
||||
inaccessible under their regular path the instant they are
|
||||
deactivated. Moreover, the `nosuid`, `nodev` and `noexec` flags configured in
|
||||
the user record are applied when the bind mount is established.
|
||||
|
||||
During activation, the user records retained on the host, the user record
|
||||
stored in the LUKS2 header (in case of the LUKS2 storage mechanism) and the
|
||||
user record stored inside the home directory in `~/.identity` are
|
||||
compared. Activation is only permitted if they match the same user and are
|
||||
signed by a recognized key. When the three instances differ in `lastChangeUSec`
|
||||
field, the newest record wins, and is propagated to the other two locations.
|
||||
|
||||
During activation the file system checker (`fsck`) appropriate for the
|
||||
selected file system is automatically invoked, ensuring the file system is in a
|
||||
healthy state before it is mounted.
|
||||
|
||||
If the UID assigned to a user does not match the owner of the home directory in
|
||||
the file system, the home directory is automatically and recursively `chown()`ed
|
||||
to the correct UID.
|
||||
|
||||
Depending on the `discard` setting of the user record either the backing
|
||||
loopback file is `fallocate()`ed during activation, or the mounted file system
|
||||
is `FITRIM`ed after mounting, to ensure the setting is correctly enforced.
|
|
@ -96,7 +96,15 @@ but downstreams are strongly advised against doing that.)
|
|||
|
||||
`systemd` defines a number of special UID ranges:
|
||||
|
||||
1. 61184…65519 → UIDs for dynamic users are allocated from this range (see the
|
||||
1. 60001…60513 → UIDs for home directories managed by
|
||||
[`systemd-homed.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-homed.service.html). UIDs
|
||||
from this range are automatically assigned to any home directory discovered,
|
||||
and persisted locally on first login. On different systems the same user
|
||||
might get different UIDs assigned in case of conflict, though it is
|
||||
attempted to make UID assignments stable, by deriving them from a hash of
|
||||
the user name.
|
||||
|
||||
2. 61184…65519 → UIDs for dynamic users are allocated from this range (see the
|
||||
`DynamicUser=` documentation in
|
||||
[`systemd.exec(5)`](https://www.freedesktop.org/software/systemd/man/systemd.exec.html)). This
|
||||
range has been chosen so that it is below the 16bit boundary (i.e. below
|
||||
|
@ -111,7 +119,7 @@ but downstreams are strongly advised against doing that.)
|
|||
user record resolving works correctly without those users being in
|
||||
`/etc/passwd`.
|
||||
|
||||
2. 524288…1879048191 → UID range for `systemd-nspawn`'s automatic allocation of
|
||||
3. 524288…1879048191 → UID range for `systemd-nspawn`'s automatic allocation of
|
||||
per-container UID ranges. When the `--private-users=pick` switch is used (or
|
||||
`-U`) then it will automatically find a so far unused 16bit subrange of this
|
||||
range and assign it to the container. The range is picked so that the upper
|
||||
|
@ -232,7 +240,8 @@ the artifacts the container manager persistently leaves in the system.
|
|||
| 5 | `tty` group | `systemd` | `/etc/passwd` |
|
||||
| 6…999 | System users | Distributions | `/etc/passwd` |
|
||||
| 1000…60000 | Regular users | Distributions | `/etc/passwd` + LDAP/NIS/… |
|
||||
| 60001…61183 | Unused | | |
|
||||
| 60001…60513 | Human Users (homed) | `systemd` | `nss-systemd`
|
||||
| 60514…61183 | Unused | | |
|
||||
| 61184…65519 | Dynamic service users | `systemd` | `nss-systemd` |
|
||||
| 65520…65533 | Unused | | |
|
||||
| 65534 | `nobody` user | Linux | `/etc/passwd` + `nss-systemd` |
|
||||
|
|
|
@ -3,17 +3,21 @@
|
|||
# You really want to adjust this to your local distribution. If you use this
|
||||
# unmodified you are not building systems safely and securely.
|
||||
|
||||
auth sufficient pam_unix.so nullok try_first_pass
|
||||
auth sufficient pam_unix.so
|
||||
-auth sufficient pam_systemd_home.so
|
||||
auth required pam_deny.so
|
||||
|
||||
account required pam_nologin.so
|
||||
-account sufficient pam_systemd_home.so
|
||||
account sufficient pam_unix.so
|
||||
account required pam_permit.so
|
||||
|
||||
password sufficient pam_unix.so nullok sha512 shadow try_first_pass try_authtok
|
||||
-password sufficient pam_systemd_home.so
|
||||
password sufficient pam_unix.so sha512 shadow try_first_pass try_authtok
|
||||
password required pam_deny.so
|
||||
|
||||
-session optional pam_keyinit.so revoke
|
||||
-session optional pam_loginuid.so
|
||||
-session optional pam_systemd_home.so
|
||||
-session optional pam_systemd.so
|
||||
session sufficient pam_unix.so
|
||||
session required pam_unix.so
|
||||
|
|
|
@ -5,7 +5,7 @@ setup:
|
|||
- sudo apt-get update -y
|
||||
- sudo apt-get build-dep -y systemd
|
||||
- sudo apt-get install -y python3-pip
|
||||
- sudo apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev
|
||||
- sudo apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev libpwquality-dev
|
||||
- pip3 install meson ninja
|
||||
- export PATH="$HOME/.local/bin/:$PATH"
|
||||
- CC=$FUZZ_CC CXX=$FUZZ_CXX meson -Dfuzzbuzz=true -Dfuzzbuzz-engine-dir=$(dirname "$FUZZ_ENGINE") -Dfuzzbuzz-engine=$(cut -d. -f1 <(basename "$FUZZ_ENGINE")) -Db_lundef=false ./build
|
||||
|
|
|
@ -0,0 +1,832 @@
|
|||
<?xml version='1.0'?>
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
|
||||
|
||||
<refentry id="homectl" conditional='ENABLE_HOMED'
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>homectl</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>homectl</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>homectl</refname>
|
||||
<refpurpose>Create, remove, change or inspect home directories</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>homectl</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="req">COMMAND</arg>
|
||||
<arg choice="opt" rep="repeat">NAME</arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>homectl</command> may be used to create, remove, change or inspect a user's home
|
||||
directory. It's primarily a command interfacing with
|
||||
<citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
which manages home directories of users.</para>
|
||||
|
||||
<para>Home directories managed by <filename>systemd-homed.service</filename> are self-contained, and thus
|
||||
include the user's full metadata record in the home's data storage itself, making them easily migratable
|
||||
between machines. In particular a home directory in itself describes a matching user record, and every
|
||||
user record managed by <filename>systemd-homed.service</filename> also implies existance and
|
||||
encapsulation of a home directory. The user account and home directory hence become the same concept. The
|
||||
following backing storage mechanisms are supported:</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>Individual LUKS2 encrypted loopback files for each user, located in
|
||||
<filename>/home/*.home</filename>. At login the file systems contained in these files are mounted,
|
||||
after the LUKS2 encrypted volume is attached. The user's password is identical to the encryption
|
||||
passphrase of the LUKS2 volume. Access to data without preceeding user authentication is thus not
|
||||
possible, not even for the systems administrator. This storage mechanism provides the strongest data
|
||||
security and is thus recommended.</para></listitem>
|
||||
|
||||
<listitem><para>Similar, but the LUKS2 encrypted file system is located on regular block device, such
|
||||
as an USB storage stick. In this mode home directories and all data they include are nicely migratable
|
||||
between machines, simply by plugging the USB stick into different systems at different
|
||||
times.</para></listitem>
|
||||
|
||||
<listitem><para>An encrypted directory using <literal>fscrypt</literal> on file systems that support it
|
||||
(at the moment this is primarily <literal>ext4</literal>), located in
|
||||
<filename>/home/*.homedir</filename>. This mechanism also provides encryption, but substantially
|
||||
weaker than the two mechanisms described above, as most file system metadata is unprotected. Moreover
|
||||
it currently does not support changing user passwords once the home directory has been
|
||||
created.</para></listitem>
|
||||
|
||||
<listitem><para>A <literal>btrfs</literal> subvolume for each user, also located in
|
||||
<filename>/home/*.homedir</filename>. This provides no encryption, but good quota
|
||||
support.</para></listitem>
|
||||
|
||||
<listitem><para>A regular directory for each user, also located in
|
||||
<filename>/home/*.homedir</filename>. This provides no encryption, but is a suitable fallback
|
||||
available on all machines, even where LUKS2, <literal>fscrypt</literal> or <literal>btrfs</literal>
|
||||
support is not available.</para></listitem>
|
||||
|
||||
<listitem><para>An individual Windows file share (CIFS) for each user.</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>Note that <filename>systemd-homed.service</filename> and <command>homectl</command> will not manage
|
||||
"classic" UNIX user accounts as created with <citerefentry
|
||||
project='man-pages'><refentrytitle>useradd</refentrytitle><manvolnum>8</manvolnum></citerefentry> or
|
||||
similar tools. In particular, this functionality is not suitable for managing system users (i.e. users
|
||||
with a UID below 1000) but is exclusive to regular ("human") users.</para>
|
||||
|
||||
<para>Note that users/home directories managed via <command>systemd-homed.service</command> do not show
|
||||
up in <filename>/etc/passwd</filename> and similar files, they are synthesized via glibc NSS during
|
||||
runtime. They are thus resolvable and may be enumerated via the <citerefentry
|
||||
project='man-pages'><refentrytitle>getent</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
tool.</para>
|
||||
|
||||
<para>This tool interfaces directly with <filename>systemd-homed.service</filename>, and may execute
|
||||
specific commands on the home directories it manages. Since every home directory managed that way also
|
||||
defines a JSON user and group record these home directories may also be inspected and enumerated via
|
||||
<citerefentry><refentrytitle>userdbctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>Home directories managed by <filename>systemd-homed.service</filename> are usually in one of two
|
||||
states, or in a transition state between them: when <literal>active</literal> they are unlocked and
|
||||
mounted, and thus accessible to the system and its programs; when <literal>inactive</literal> they are
|
||||
not mounted and thus not accessible. Activation happens automatically at log-in of the user and usually
|
||||
can only complete after a password (or other authentication token) has been supplied. Deactivation
|
||||
happens after the user fully logged out. A home directory remains active as long as the user is logged in
|
||||
at least once, i.e. has at least one login session. When the user logs in a second time simultaneously
|
||||
the home directory remains active. It is deactivated only after the last of the user's sessions
|
||||
ends.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
<para>The following general options are understood (further options that control the various properties
|
||||
of user records managed by <filename>systemd-homed.service</filename> are documented further
|
||||
down):</para>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--identity=</option><replaceable>FILE</replaceable></term>
|
||||
|
||||
<listitem><para>Read the user's JSON record from the specified file. If passed as
|
||||
<literal>-</literal> reads the user record from standard input. The supplied JSON object must follow
|
||||
the structure documented on <ulink url="https://systemd.io/USER_RECORDS">JSON User
|
||||
Records</ulink>. This option may be used in conjunction with the <command>create</command> and
|
||||
<command>update</command> commands (see below), where it allows configuring the user record in JSON
|
||||
as-is, instead of setting the individual user record properties (see below).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--json=</option><replaceable>FORMAT</replaceable></term>
|
||||
<term><option>-J</option></term>
|
||||
|
||||
<listitem><para>Controls whether to output the user record in JSON format, if the
|
||||
<command>inspect</command> command (see below) is used. Takes one of <literal>pretty</literal>,
|
||||
<literal>short</literal> or <literal>off</literal>. If <literal>pretty</literal> human-friendly
|
||||
whitespace and newlines are inserted in the output to make the JSON data more readable. If
|
||||
<literal>short</literal> all superfluous whitespace is suppressed. If <literal>off</literal> (the
|
||||
default) the user information is not shown in JSON format but in a friendly human readable formatting
|
||||
instead. The <option>-J</option> option picks <literal>pretty</literal> when run interactively and
|
||||
<literal>short</literal> otherwise.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--export-format=</option><replaceable>FORMAT</replaceable></term>
|
||||
<term><option>-E</option></term>
|
||||
<term><option>-EE</option></term>
|
||||
|
||||
<listitem><para>When used with the <command>inspect</command> verb in JSON mode (see above) may be
|
||||
used to suppress certain aspects of the JSON user record on output. Specifically, if
|
||||
<literal>stripped</literal> format is used the binding and runtime fields of the record are
|
||||
removed. If <literal>minimal</literal> format is used the cryptographic signature is removed too. If
|
||||
<literal>full</literal> format is used the full JSON record is shown (this is the default). This
|
||||
option is useful for copying an existing user record to a different system in order to create a
|
||||
similar user there with the same settings. Specifically: <command>homectl inspect -EE | ssh
|
||||
root@othersystem homectl create -i-</command> may be used as simple command line for replicating a
|
||||
user on another host. <option>-E</option> is equivalent to <option>-j --export-format=stripped</option>,
|
||||
<option>-EE</option> to <option>-j --export-format=minimal</option>. Note that when replicating user
|
||||
accounts user records acquired in <literal>stripped</literal> mode will retain the original
|
||||
cryptographic signatures and thus may only modified when the private key to update them is available
|
||||
on the destination machine. When replicating users in <literal>minimal</literal> mode the signature
|
||||
is remove during the replication and thus it is implicitly signed with the key of the destination
|
||||
machine and thus may be updated there without any private key replication.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="user-system-options.xml" xpointer="host" />
|
||||
<xi:include href="user-system-options.xml" xpointer="machine" />
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||
<xi:include href="standard-options.xml" xpointer="no-legend" />
|
||||
<xi:include href="standard-options.xml" xpointer="no-ask-password" />
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>User Record Properties</title>
|
||||
|
||||
<para>The following options control various properties of the user records/home directories that
|
||||
<filename>systemd-homed.service</filename> manages. These switches may be used in conjunction with the
|
||||
<command>create</command> and <command>update</command> commands for configuring various aspects of the
|
||||
home directory and the user account:</para>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--real-name=</option><replaceable>NAME</replaceable></term>
|
||||
<term><option>-c</option> <replaceable>NAME</replaceable></term>
|
||||
|
||||
<listitem><para>The real name for the user. This corresponds with the GECOS field on classic UNIX NSS
|
||||
records.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--realm=</option><replaceable>REALM</replaceable></term>
|
||||
|
||||
<listitem><para>The realm for the user. The realm associates a user with a specific organization or
|
||||
installation, and allows distuingishing users of the same name defined in different contexts. The
|
||||
realm can be any string that also qualifies as valid DNS domain name, and it is recommended to use
|
||||
the organization's or installation's domain name for this purpose, but this is not enforced nor
|
||||
required. On each system only a single user of the same name may exist, and if a user with the same
|
||||
name and realm is seen it is assumed to refer to the same user while a user with the same name but
|
||||
different realm is considered a different user. Note that this means that two users sharing the same
|
||||
name but with distinct realms are not allowed on the same system. Assigning a realm to a user is
|
||||
optional.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--email-address=</option><replaceable>EMAIL</replaceable></term>
|
||||
|
||||
<listitem><para>Takes an electronic mail address to associate with the user. On log-in the
|
||||
<varname>$EMAIL</varname> environment variable is initialized from this value.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--location=</option><replaceable>TEXT</replaceable></term>
|
||||
|
||||
<listitem><para>Takes location specification for this user. This is free-form text, which might or
|
||||
might not be usable by geo-location applications. Example: <option>--location="Berlin,
|
||||
Germany"</option> or <option>--location="Basement, Room 3a"</option></para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--icon-name=</option><replaceable>ICON</replaceable></term>
|
||||
|
||||
<listitem><para>Takes an icon name to associate with the user, following the scheme defined by the <ulink
|
||||
url="https://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html">Icon Naming
|
||||
Specification</ulink>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--home-dir=</option><replaceable>PATH</replaceable></term>
|
||||
<term><option>-d</option><replaceable>PATH</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a path to use as home directory for the user. Note that this is the directory
|
||||
the user's home directory is mounted to while the user is logged in. This is not where the user's
|
||||
data is actually stored, see <option>--image-path=</option> for that. If not specified defaults to
|
||||
<filename>/home/$USER</filename>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--uid=</option><replaceable>UID</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a preferred numeric UNIX UID to assign this user. If a user is to be created
|
||||
with the specified UID and it is already taken by a different user on the local system then creation
|
||||
of the home directory is refused. Note though, if after creating the home directory it is used on a
|
||||
different system and the configured UID is taken by another user there, then
|
||||
<command>systemd-homed</command> may assign the user a different UID on that system. The specified
|
||||
UID must be outside of the system user range. It is recommended to use the 60001…60513 UID range for
|
||||
this purpose. If not specified the UID is automatically picked. When logging in and the home
|
||||
directory is found to be owned by a UID not matching the user's assigned one the home directory and
|
||||
all files and directories inside it will have their ownership changed automatically before login
|
||||
completes.</para>
|
||||
|
||||
<para>Note that users managed by <command>systemd-homed</command> always have a matching group
|
||||
associated with the same name as well as a GID matching the UID of the user. Thus, configuring the
|
||||
GID separately is not permitted.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--member-of=</option><replaceable>GROUP</replaceable></term>
|
||||
<term><option>-G</option> <replaceable>GROUP</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a comma-separated list of auxiliary UNIX groups this user shall belong
|
||||
to. Example: <option>--member-of=wheel</option> to provide the user with administrator
|
||||
privileges. Note that <command>systemd-homed</command> does not manage any groups besides a group
|
||||
matching the user in name and numeric UID/GID. Thus any groups listed here must be registered
|
||||
independently, for example with <citerefentry
|
||||
project='man-pages'><refentrytitle>groupadd</refentrytitle><manvolnum>8</manvolnum></citerefentry>. If
|
||||
non-existant groups that are listed there are ignored. This option may be used more than once, in
|
||||
which case all specified group lists are combined.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--skel=</option><replaceable>PATH</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a file system path to a directory. Specifies the skeleton directory to
|
||||
initialize the home directory with. All files and directories in the specified are copied into any
|
||||
newly create home directory. If not specified defaults to
|
||||
<filename>/etc/skel/</filename>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--shell=</option><replaceable>SHELL</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a file system path. Specifies the shell binary to execute on terminal
|
||||
logins. If not specified defaults to <filename>/bin/bash</filename>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--setenv=</option><replaceable>VARIABLE</replaceable>=<replaceable>VALUE</replaceable></term>
|
||||
|
||||
<listitem><para>Takes an environment variable assignment to set for all user processes. Note that a
|
||||
number of other settings also result in environment variables to be set for the user, including
|
||||
<option>--email=</option>, <option>--timezone=</option> and <option>--language=</option>. May be used
|
||||
multiple times to set multiple environment variables.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--timezone=</option><replaceable>TIMEZONE</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a timezone specification as string that sets the timezone for the specified
|
||||
user. Expects a `tzdata` location string. When the user logs in the <varname>$TZ</varname>
|
||||
environment variable is initialized from this setting. Example:
|
||||
<option>--timezone=Europe/Amsterdam</option> will result in the environment variable
|
||||
<literal>TZ=:Europe/Amsterdam</literal>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--language=</option><replaceable>LANG</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a specifier indicating the preferred language of the user. The
|
||||
<varname>$LANG</varname> environment variable is initialized from this value on login, and thus a
|
||||
value suitable for this environment variable is accepted here, for example
|
||||
<option>--language=de_DE.UTF8</option></para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--ssh-authorized-keys=</option><replaceable>KEYS</replaceable></term>
|
||||
<listitem><para>Either takes a SSH authorized key line to associate with the user record or a
|
||||
<literal>@</literal> character followed by a path to a file to read one or more such lines from. SSH
|
||||
keys configured this way are made available to SSH to permit access to this home directory and user
|
||||
record. This option may be used more than once to configure multiple SSH keys.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--pkcs11-token-uri=</option><replaceable>URI</replaceable></term>
|
||||
<listitem><para>Takes an RFC 7512 PKCS#11 URI referencing a security token (e.g. YubiKey or PIV
|
||||
smartcard) that shall be able to unlock the user account. The security token URI should reference a
|
||||
security token with exactly one pair of X.509 certificate and private key. A random secret key is
|
||||
then generated, encrypted with the public key of the X.509 certificate, and stored as part of the
|
||||
user record. At login time it is decrypted with the PKCS#11 module and then used to unlock the
|
||||
account and associated resources. See below for an example how to set up authentication with security
|
||||
token.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--locked=</option><replaceable>BOOLEAN</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. Specifies whether this user account shall be locked. If
|
||||
true logins into this account are prohibited, if false (the default) they are permitted (of course,
|
||||
only if authorization otherwise succeeds).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--not-before=</option><replaceable>TIMESTAMP</replaceable></term>
|
||||
<term><option>--not-after=</option><replaceable>TIMESTAMP</replaceable></term>
|
||||
|
||||
<listitem><para>These options take a timestamp string, in the format documented in
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry> and
|
||||
configures points in time before and after logins into this account are not
|
||||
permitted.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--rate-limit-interval=</option><replaceable>SECS</replaceable></term>
|
||||
<term><option>--rate-limit-burst=</option><replaceable>NUMBER</replaceable></term>
|
||||
|
||||
<listitem><para>Configures a rate limit on authentication attempts for this user. If the user
|
||||
attempts to authenticate more often than the specified number, on a specific system, within the
|
||||
specified time interval authentication is refused until the time interval passes. Defaults to 10
|
||||
times per 1min.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--password-hint=</option><replaceable>TEXT</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a password hint to store alongside the user record. This string is stored
|
||||
accessible only to privileged users and the user itself and may not be queried by other users.
|
||||
Example: <option>--password-hint="My first pet's name"</option></para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--enforce-password-policy=</option><replaceable>BOOL</replaceable></term>
|
||||
<term><option>-P</option></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. Configures whether to enforce the system's password policy
|
||||
for this user, regarding quality and strength of selected passwords. Defaults to
|
||||
on. <option>-P</option> is short for
|
||||
<option>---enforce-password-policy=no</option>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--password-change-now=</option><replaceable>BOOL</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. If true the user is asked to change their password on next
|
||||
login.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--password-change-min=</option><replaceable>TIME</replaceable></term>
|
||||
<term><option>--password-change-max=</option><replaceable>TIME</replaceable></term>
|
||||
<term><option>--password-change-warn=</option><replaceable>TIME</replaceable></term>
|
||||
<term><option>--password-change-inactive=</option><replaceable>TIME</replaceable></term>
|
||||
|
||||
<listitem><para>Each of these options takes a time span specification as argument (in the syntax
|
||||
documented in
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>5</manvolnum></citerefentry>) and
|
||||
configure various aspects of the user's password expiration policy. Specifically,
|
||||
<option>--password-change-min=</option> configures how much time has to pass after changing the
|
||||
password of the user until the password may be changed again. If the user tries to change their
|
||||
password before this time passes the attempt is refused. <option>--password-change-max=</option>
|
||||
configures how much time has to pass after the the password is changed until the password expires and
|
||||
needs to be changed again. After this time passes any attempts to log in may only proceed after the
|
||||
password is changed. <option>--password-change-warn=</option> specifies how much earlier than then
|
||||
the time configured with <option>--password-change-max=</option> the user is warned at login to
|
||||
change their password as it will expire soon. Finally <option>--password-change-inactive=</option>
|
||||
configures the time which has to pass after the password as expired until the user is not permitted
|
||||
to log in or change the password anymore. Note that these options only apply to password
|
||||
authentication, and do not apply to other forms of authentication, for example PKCS#11-based security
|
||||
token authentication.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--disk-size=</option><replaceable>BYTES</replaceable></term>
|
||||
<listitem><para>Either takes a size in bytes as argument (possibly using the usual K, M, G, …
|
||||
suffixes for 1024 base values), or a percentage value and configures the disk space to assign to the
|
||||
user. If a percentage value is specified (i.e. the argument suffixed with <literal>%</literal>) it is
|
||||
taken relative to the available disk space of the backing file system. If the LUKS2 backend is used
|
||||
this configures the size of the loopback file and file system contained therein. For the other
|
||||
storage backends configures disk quota using the filesystem's native quota logic, if available. If
|
||||
not specified, defaults to 85% of the available disk space for the LUKS2 backend and to no quota for
|
||||
the others.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--access-mode=</option><replaceable>MODE</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a UNIX file access mode written in octal. Configures the access mode of the
|
||||
home directory itself. Note that this is only used when the directory is first created, and the user
|
||||
may change this any time afterwards. Example:
|
||||
<option>--access-mode=0700</option></para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--umask=</option><replaceable>MASK</replaceable></term>
|
||||
|
||||
<listitem><para>Takes the access mode mask (in octal syntax) to apply to newly created files and
|
||||
directories of the user ("umask"). If set this controls the initial umask set for all login sessions of
|
||||
the user, possibly overriding the system's defaults.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--nice=</option><replaceable>NICE</replaceable></term>
|
||||
|
||||
<listitem><para>Takes the numeric scheduling priority ("nice level") to apply to the processes of the user at login
|
||||
time. Takes a numeric value in the range -20 (highest priority) to 19 (lowest priority).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--rlimit=</option><replaceable>LIMIT</replaceable>=<replaceable>VALUE</replaceable><optional>:<replaceable>VALUE</replaceable></optional></term>
|
||||
|
||||
<listitem><para>Allows configuration of resource limits for processes of this user, see <citerefentry
|
||||
project='man-pages'><refentrytitle>getrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry>
|
||||
for details. Takes a resource limit name (e.g. <literal>LIMIT_NOFILE</literal>) followed by an equal
|
||||
sign, followed by a numeric limit. Optionally, separated by colon a second numeric limit may be
|
||||
specified. If two are specified this refers to the soft and hard limits, respectively. If only one
|
||||
limit is specified the setting sets both limits in one.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--tasks-max=</option><replaceable>TASKS</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a non-zero unsigned integer as argument. Configures the maximum numer of tasks
|
||||
(i.e. processes and threads) the user may have at any given time. This limit applies to all tasks
|
||||
forked off the user's sessions, even if they change user identity via <citerefentry
|
||||
project='man-pages'><refentrytitle>su</refentrytitle><manvolnum>1</manvolnum></citerefentry> or a
|
||||
similar tool. Use <option>--rlimit=LIMIT_NPROC=</option> to place a limit on the tasks actually
|
||||
running under the UID of the user, thus excluding any child processes that might have changed user
|
||||
identity. This controls the <varname>TasksMax=</varname> settting of the per-user systemd slice unit
|
||||
<filename>user-$UID.slice</filename>. See
|
||||
<citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for further details.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--memory-high=</option><replaceable>BYTES</replaceable></term>
|
||||
<term><option>--memory-max=</option><replaceable>BYTES</replaceable></term>
|
||||
|
||||
<listitem><para>Set a limit on the memory a user may take up on a system at any given time in bytes
|
||||
(the usual K, M, G, … suffixes are supported, to the base of 1024). This includes all memory used by
|
||||
the user itself and all processes they forked off that changed user credentials. This controls the
|
||||
<varname>MemoryHigh=</varname> and <varname>MemoryMax=</varname> settings of the per-user systemd
|
||||
slice unit <filename>user-$UID.slice</filename>. See
|
||||
<citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for further details.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--cpu-weight=</option><replaceable>WEIGHT</replaceable></term>
|
||||
<term><option>--io-weight=</option><replaceable>WEIGHT</replaceable></term>
|
||||
|
||||
<listitem><para>Set a CPU and IO scheduling weights of the processes of the user, including those of
|
||||
processes forked off by the user that changed user credentials. Takes a numeric value in the range
|
||||
1…10000. This controls the <varname>CPUWeight=</varname> and <varname>IOWeight=</varname> settings of
|
||||
the per-user systemd slice unit <filename>user-$UID.slice</filename>. See
|
||||
<citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for further details.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--storage=</option><replaceable>STORAGE</replaceable></term>
|
||||
|
||||
<listitem><para>Selects the storage mechanism to use for this home directory. Takes one of
|
||||
<literal>luks</literal>, <literal>fscrypt</literal>, <literal>directory</literal>,
|
||||
<literal>subvolume</literal>, <literal>cifs</literal>. For details about these mechanisms, see
|
||||
above. If a new home directory is created and the storage type is not specifically specified defaults
|
||||
to <literal>luks</literal> if supported, <literal>subvolume</literal> as first fallback if supported,
|
||||
and <literal>directory</literal> if not.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--image-path=</option><replaceable>PATH</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a file system path. Configures where to place the user's home directory. When
|
||||
LUKS2 storage is used refers to the path to the loopback file, otherwise to the path to the home
|
||||
directory. When unspecified defaults to <filename>/home/$USER.home</filename> when LUKS storage is
|
||||
used and <filename>/home/$USER.homedir</filename> for the other storage mechanisms. Not defined for
|
||||
the <literal>cifs</literal> storage mechanism. To use LUKS2 storage on a regular block device (for
|
||||
example a USB stick) pass the path to the block device here.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--fs-type=</option><replaceable>TYPE</replaceable></term>
|
||||
|
||||
<listitem><para>When LUKS2 storage is used configures the file system type to use inside the home
|
||||
directory LUKS2 container. One of <literal>ext4</literal>, <literal>xfs</literal>,
|
||||
<literal>btrfs</literal>. If not specified defaults to <literal>ext4</literal>. Note that
|
||||
<literal>xfs</literal> is not recommended as its support for file system resizing is too
|
||||
limited.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--luks-discard=</option><replaceable>BOOL</replaceable></term>
|
||||
|
||||
<listitem><para>When LUKS2 storage is used configures whether to enable the
|
||||
<literal>discard</literal> feature of the file system. If enabled the file system on top of the LUKS2
|
||||
volume will report empty block information to LUKS2 and the loopback file below, ensuring that empty
|
||||
space in the home directory is returned to the backing file system below the LUKS2 volume, resulting
|
||||
in a "sparse" loopback file. This option mostly defaults to off, since this permits over-committing
|
||||
home directories which results in I/O errors if the underlying file system runs full while the upper
|
||||
file system wants to allocate a block. Such I/O errors are generally not handled well by file systems
|
||||
nor applications. When LUKS2 storage is used on top of regular block devices (instead of on top a
|
||||
loopback file) the discard logic defaults to on.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--luks-cipher=</option><replaceable>CIPHER</replaceable></term>
|
||||
<term><option>--luks-cipher-mode=</option><replaceable>MODE</replaceable></term>
|
||||
<term><option>--luks-volume-key-size=</option><replaceable>BITS</replaceable></term>
|
||||
<term><option>--luks-pbkdf-type=</option><replaceable>TYPE</replaceable></term>
|
||||
<term><option>--luks-pbkdf-hash-algorithm=</option><replaceable>ALGORITHM</replaceable></term>
|
||||
<term><option>--luks-pbkdf-time-cost=</option><replaceable>SECONDS</replaceable></term>
|
||||
<term><option>--luks-pbkdf-memory-cost=</option><replaceable>BYTES</replaceable></term>
|
||||
<term><option>--luks-pbkdf-parallel-threads=</option><replaceable>THREADS</replaceable></term>
|
||||
|
||||
<listitem><para>Configures various cryptographic parameters for the LUKS2 storage mechanism. See
|
||||
<citerefentry
|
||||
project='man-pages'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
for details on the specific attributes.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--nosuid=</option><replaceable>BOOL</replaceable></term>
|
||||
<term><option>--nodev=</option><replaceable>BOOL</replaceable></term>
|
||||
<term><option>--noexec=</option><replaceable>BOOL</replaceable></term>
|
||||
|
||||
<listitem><para>Configures the <literal>nosuid</literal>, <literal>nodev</literal> and
|
||||
<literal>noexec</literal> mount options for the home directories. By default <literal>nodev</literal>
|
||||
and <literal>nosuid</literal> are on, while <literal>noexec</literal> is off. For details about these
|
||||
mount options see <citerefentry
|
||||
project='man-pages'><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--cifs-domain=</option><replaceable>DOMAIN</replaceable></term>
|
||||
<term><option>--cifs-user-name=</option><replaceable>USER</replaceable></term>
|
||||
<term><option>--cifs-service=</option><replaceable>SERVICE</replaceable></term>
|
||||
|
||||
<listitem><para>Configures the Windows File Sharing (CIFS) domain and user to associate with the home
|
||||
directory/user account, as well as the file share ("service") to mount as directory. The latter is used when
|
||||
<literal>cifs</literal> storage is selected.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--stop-delay=</option><replaceable>SECS</replaceable></term>
|
||||
|
||||
<listitem><para>Configures the time the per-user service manager shall continue to run after the all
|
||||
sessions of the user ended. The default is configured in
|
||||
<citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> (for
|
||||
home directories of LUKS2 storage located on removable media this defaults to 0 though). A longer
|
||||
time makes sure quick, repetitive logins are more efficient as the user's service manager doesn't
|
||||
have to be started every time.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--kill-processes=</option><replaceable>BOOL</replaceable></term>
|
||||
|
||||
<listitem><para>Configures whether to kill all processes of the user on logout. The default is
|
||||
configured in
|
||||
<citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--auto-login=</option><replaceable>BOOL</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. Configures whether the graphical UI of the system should
|
||||
automatically log this user in if possible. Defaults to off. If less or more than one user is marked
|
||||
this way automatic login is disabled.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Commands</title>
|
||||
|
||||
<para>The following commands are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>list</command></term>
|
||||
|
||||
<listitem><para>List all home directories (along with brief details) currently managed by
|
||||
<filename>systemd-homed.service</filename>. This command is also executed if none is specified on the
|
||||
command line. (Note that the list of users shown by this command does not include users managed by
|
||||
other subsystems, such as system users or any traditional users listed in
|
||||
<filename>/etc/passwd</filename>.)</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>activate</command> <replaceable>USER</replaceable> [<replaceable>USER…</replaceable>]</term>
|
||||
|
||||
<listitem><para>Activate one or more home directories. The home directories of each listed user will
|
||||
be activated and made available under their mount points (typically in
|
||||
<filename>/home/$USER</filename>). Note that any home activated this way stays active indefinitely,
|
||||
until it is explicitly deactivated again (with <command>deactivate</command>, see below), or the user
|
||||
logs in and out again and it thus is deactivated due to the automatic deactivation-on-logout
|
||||
logic.</para>
|
||||
|
||||
<para>Activation of a home directory involves various operations that depend on the selected storage
|
||||
mechanism. If the LUKS2 mechanism is used, this generally involves: inquiring the user for a
|
||||
password, setting up a loopback device, validating and activating the LUKS2 volume, checking the file
|
||||
system, mounting the file system, and potentiatlly changing the ownership of all included files to
|
||||
the correct UID/GID.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>deactivate</command> <replaceable>USER</replaceable> [<replaceable>USER…</replaceable>]</term>
|
||||
|
||||
<listitem><para>Deactivate one or more home directories. This undoes the effect of
|
||||
<command>activate</command>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>inspect</command> <replaceable>USER</replaceable> [<replaceable>USER…</replaceable>]</term>
|
||||
|
||||
<listitem><para>Show various details about the specified home directories. This shows various
|
||||
information about the home directory and its user account, including runtime data such as current
|
||||
state, disk use and similar. Combine with <option>--json=</option> to show the detailed JSON user
|
||||
record instead, possibly combined with <option>--export-format=</option> to suppress certain aspects
|
||||
of the output.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>authenticate</command> <replaceable>USER</replaceable> [<replaceable>USER…</replaceable>]</term>
|
||||
|
||||
<listitem><para>Validate authentication credentials of a home directory. This queries the caller for
|
||||
a password (or similar) and checks that it correctly unlocks the home directory. This leaves the home
|
||||
directory in the state it is in, i.e. it leaves the home directory in inactive state if it was
|
||||
inactive before, and in active state if it was active before.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>create</command> <replaceable>USER</replaceable></term>
|
||||
<term><command>create</command> <option>--identity=</option><replaceable>PATH</replaceable> <optional><replaceable>USER</replaceable></optional></term>
|
||||
|
||||
<listitem><para>Create a new home directory/user account of the specified name. Use the various
|
||||
user record property options (as documented above) to control various aspects of the home directory
|
||||
and its user accounts.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>remove</command> <replaceable>USER</replaceable></term>
|
||||
|
||||
<listitem><para>Remove a home directory/user account. This will remove both the home directory's user
|
||||
record and the home directory itself, and thus delete all files and directories owned by the
|
||||
user.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>update</command> <replaceable>USER</replaceable></term>
|
||||
<term><command>update</command> <option>--identity=</option><replaceable>PATH</replaceable> <optional><replaceable>USER</replaceable></optional></term>
|
||||
|
||||
<listitem><para>Update a home directory/user account. Use the various user record property options
|
||||
(as documented above) to make changes to the account, or alternatively provide a full, updated JSON
|
||||
user record via the <option>--identity=</option> option.</para>
|
||||
|
||||
<para>Note that changes to user records not signed by a cryptographic private key available locally
|
||||
are not permitted, unless <option>--identity=</option> is used with a user record that is already
|
||||
correctly signed by a recognized private key.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>passwd</command> <replaceable>USER</replaceable></term>
|
||||
|
||||
<listitem><para>Change the password of the specified home direcory/user account.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>resize</command> <replaceable>USER</replaceable> <replaceable>BYTES</replaceable></term>
|
||||
|
||||
<listitem><para>Change the disk space assigned to the specified home directory. If the LUKS2 storage
|
||||
mechanism is used this will automatically resize the loopback file and the file system contained
|
||||
within. Note that if <literal>ext4</literal> is used inside of the LUKS2 volume, it is necessary to
|
||||
deactivate the home directory before shrinking it (i.e the user has to log out). Growing can be done
|
||||
while the home directory is active. If <literal>xfs</literal> is used inside of the LUKS2 volume the
|
||||
home directory may not be shrunk whatsoever. On all three of <literal>ext4</literal>,
|
||||
<literal>xfs</literal> and <literal>btrfs</literal> the home directory may be grown while the user is
|
||||
logged in, and on the latter also shrunk while the user is logged in. If the
|
||||
<literal>subvolume</literal>, <literal>directory</literal>, <literal>fscrypt</literal> storage
|
||||
mechanisms are used, resizing will change file system quota.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>lock</command> <replaceable>USER</replaceable></term>
|
||||
|
||||
<listitem><para>Temporarily suspend access to the user's home directory and remove any associated
|
||||
cryptographic keys from memory. Any attempts to access the user's home directory will stall until the
|
||||
home directory is unlocked again (i.e. re-authenticated). This functionality is primarily intended to
|
||||
be used during system suspend to make sure the user's data cannot be accessed until the user
|
||||
re-authenticates on resume. This operation is only defined for home directories that use the LUKS2
|
||||
storage mechanism.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>unlock</command> <replaceable>USER</replaceable></term>
|
||||
|
||||
<listitem><para>Resume access to the user's home directory again, undoing the effect of
|
||||
<command>lock</command> above. This requires authentication of the user, as the cryptographic keys
|
||||
required for access to the home directory need to be reacquired.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>lock-all</command></term>
|
||||
|
||||
<listitem><para>Execute the <command>lock</command> command on all suitable home directories at
|
||||
once. This operation is generally executed on system suspend (i.e. by <command>systemctl
|
||||
suspend</command> and related commands), to ensure all active user's cryptographic keys for accessing
|
||||
their home directories are removed from memory.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>with</command> <replaceable>USER</replaceable> <replaceable>COMMAND…</replaceable></term>
|
||||
|
||||
<listitem><para>Activate the specified user's home directory, run the specified command (under the
|
||||
caller's identity, not the specified user's) and deactivate the home directory afterwards again
|
||||
(unless the user is logged in otherwise). This command is useful for running privileged backup
|
||||
scripts and such, but requires authentication with the user's credentials in order to be able to
|
||||
unlock the user's home directory.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Exit status</title>
|
||||
|
||||
<para>On success, 0 is returned, a non-zero failure code otherwise.</para>
|
||||
</refsect1>
|
||||
|
||||
<xi:include href="less-variables.xml" />
|
||||
|
||||
<refsect1>
|
||||
<title>Examples</title>
|
||||
|
||||
<example>
|
||||
<title>Create a user <literal>waldo</literal> in the administrator group <literal>wheel</literal>, and
|
||||
assign 500 MiB disk space to them.</title>
|
||||
|
||||
<programlisting>homectl create waldo --real-name="Waldo McWaldo" -G wheel --disk-size=500M</programlisting>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Create a user <literal>wally</literal> on a USB stick, and assign a maximum of 500 concurrent
|
||||
tasks to them.</title>
|
||||
|
||||
<programlisting>homectl create wally --real-name="Wally McWally" --image-path=/dev/disk/by-id/usb-SanDisk_Ultra_Fit_476fff954b2b5c44-0:0 --tasks-max=500</programlisting>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Change nice level of user <literal>odlaw</literal> to +5 and make sure the environment variable
|
||||
<varname>$SOME</varname> is set to the string <literal>THING</literal> for them on login.</title>
|
||||
|
||||
<programlisting>homectl update odlaw --nice=5 --setenv=SOME=THING</programlisting>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Set up authentication with a YubiKey security token:</title>
|
||||
|
||||
<programlisting># Clear the Yubikey from any old keys (careful!)
|
||||
ykman piv reset
|
||||
|
||||
# Generate a new private/public key pair on the device, store the public key in 'pubkey.pem'.
|
||||
ykman piv generate-key -a RSA2048 9d pubkey.pem
|
||||
|
||||
# Create a self-signed certificate from this public key, and store it on the device.
|
||||
ykman piv generate-certificate --subject "Knobelei" 9d pubkey.pem
|
||||
|
||||
# We don't need the publibc key on disk anymore
|
||||
rm pubkey.pem
|
||||
|
||||
# Check if the newly create key on the Yubikey shows up as token in PKCS#11. Have a look at the output, and
|
||||
# copy the resulting token URI to the clipboard.
|
||||
p11tool --list-tokens
|
||||
|
||||
# Allow the security token referenced by the determined PKCS#11 URI to unlock the account of user
|
||||
# 'lafcadio'. (Replace the '…' by the URI from the clipboard.)
|
||||
homectl update lafcadio --pkcs11-token-uri=…</programlisting>
|
||||
</example>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>userdbctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>useradd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
|
@ -0,0 +1,130 @@
|
|||
<?xml version='1.0'?> <!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
|
||||
|
||||
<refentry id="pam_systemd_home" conditional='HAVE_PAM'>
|
||||
|
||||
<refentryinfo>
|
||||
<title>pam_systemd_home</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>pam_systemd_home</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>pam_systemd_home</refname>
|
||||
<refpurpose>Automatically mount home directories managed by <filename>systemd-homed.service</filename> on
|
||||
login, and unmount them on logout</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><filename>pam_systemd_home.so</filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>pam_systemd_home</command> ensures that home directories managed by
|
||||
<citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
are automatically activated (mounted) on user login, and are deactivated (unmounted) when the last
|
||||
session of the user ends.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
<para>The following options are understood:</para>
|
||||
|
||||
<variablelist class='pam-directives'>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>suspend=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. If true, the home directory of the user will be suspended
|
||||
automatically during system suspend; if false it will remain active. Automatic suspending of the home
|
||||
directory improves security substantially as secret key material is automatically removed from memory
|
||||
before the system is put to sleep and must be re-acquired (by user re-authentication) when coming
|
||||
back from suspend. It is recommended to set this parameter for all PAM applications that have support
|
||||
for automatically re-authenticating via PAM on system resume. If multiple sessions of the same user
|
||||
are open in parallel the user's home directory will be left unsuspended on system suspend as soon as
|
||||
at least one of the sessions does not set this parameter. Defaults to off.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>debug</varname><optional>=</optional></term>
|
||||
|
||||
<listitem><para>Takes an optional boolean argument. If yes or without the argument, the module will log
|
||||
debugging information as it operates.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Module Types Provided</title>
|
||||
|
||||
<para>The module provides all four management operations: <option>auth</option>, <option>account</option>,
|
||||
<option>session</option>, <option>password</option>.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Environment</title>
|
||||
|
||||
<para>The following environment variables are initialized by the module and available to the processes of the
|
||||
user's session:</para>
|
||||
|
||||
<variablelist class='environment-variables'>
|
||||
<varlistentry>
|
||||
<term><varname>$SYSTEMD_HOME=1</varname></term>
|
||||
|
||||
<listitem><para>Indicates that the user's home directory is managed by <filename>systemd-homed.service</filename>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Example</title>
|
||||
|
||||
<para>Here's an example PAM configuration fragment that permits users managed by
|
||||
<filename>systemd-homed.service</filename> to log in:</para>
|
||||
|
||||
<programlisting>#%PAM-1.0
|
||||
auth sufficient pam_unix.so
|
||||
-auth sufficient pam_systemd_home.so
|
||||
auth required pam_deny.so
|
||||
|
||||
account required pam_nologin.so
|
||||
-account sufficient pam_systemd_home.so
|
||||
account sufficient pam_unix.so
|
||||
account required pam_permit.so
|
||||
|
||||
-password sufficient pam_systemd_home.so
|
||||
password sufficient pam_unix.so sha512 shadow try_first_pass try_authtok
|
||||
password required pam_deny.so
|
||||
|
||||
-session optional pam_keyinit.so revoke
|
||||
-session optional pam_loginuid.so
|
||||
-session optional pam_systemd_home.so
|
||||
-session optional pam_systemd.so
|
||||
session required pam_unix.so</programlisting>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>homed.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>homectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>pam_systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>pam.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>pam.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
|
@ -17,6 +17,7 @@ manpages = [
|
|||
['environment.d', '5', [], 'ENABLE_ENVIRONMENT_D'],
|
||||
['file-hierarchy', '7', [], ''],
|
||||
['halt', '8', ['poweroff', 'reboot'], ''],
|
||||
['homectl', '1', [], 'ENABLE_HOMED'],
|
||||
['hostname', '5', [], ''],
|
||||
['hostnamectl', '1', [], 'ENABLE_HOSTNAMED'],
|
||||
['hwdb', '7', [], 'ENABLE_HWDB'],
|
||||
|
@ -45,6 +46,7 @@ manpages = [
|
|||
['nss-systemd', '8', ['libnss_systemd.so.2'], 'ENABLE_NSS_SYSTEMD'],
|
||||
['os-release', '5', [], ''],
|
||||
['pam_systemd', '8', [], 'HAVE_PAM'],
|
||||
['pam_systemd_home', '8', [], 'HAVE_PAM'],
|
||||
['portablectl', '1', [], 'ENABLE_PORTABLED'],
|
||||
['pstore.conf', '5', ['pstore.conf.d'], 'ENABLE_PSTORE'],
|
||||
['repart.d', '5', [], ''],
|
||||
|
@ -708,6 +710,7 @@ manpages = [
|
|||
'8',
|
||||
['systemd-hibernate-resume'],
|
||||
'ENABLE_HIBERNATE'],
|
||||
['systemd-homed.service', '8', ['systemd-homed'], 'ENABLE_HOMED'],
|
||||
['systemd-hostnamed.service', '8', ['systemd-hostnamed'], 'ENABLE_HOSTNAMED'],
|
||||
['systemd-hwdb', '8', [], 'ENABLE_HWDB'],
|
||||
['systemd-id128', '1', [], ''],
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version='1.0'?> <!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
|
||||
|
||||
<refentry id="systemd-homed.service" conditional='ENABLE_HOMED'>
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-homed.service</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-homed.service</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-homed.service</refname>
|
||||
<refname>systemd-homed</refname>
|
||||
<refpurpose>Home Directory/User Account Manager</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><filename>systemd-homed.service</filename></para>
|
||||
<para><filename>/usr/lib/systemd/systemd-homed</filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>systemd-homed</command> is a system service that may be used to create, remove, change or
|
||||
inspect home directories.</para>
|
||||
|
||||
<para>Most of <command>systemd-homed</command>'s functionality is accessible through the
|
||||
<citerefentry><refentrytitle>homectl</refentrytitle><manvolnum>1</manvolnum></citerefentry> command.</para>
|
||||
|
||||
<para>See the <ulink url="https://systemd.io/HOME_DIRECTORY">Home Directories</ulink> documentation for
|
||||
details about the format and design of home directories managed by
|
||||
<filename>systemd-homed.service</filename>.</para>
|
||||
|
||||
<para>Each home directory managed by <filename>systemd-homed.service</filename> synthesizes a local user
|
||||
and group. These are made available to the system using the <ulink
|
||||
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>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>homectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>userdbctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
</refentry>
|
93
meson.build
93
meson.build
|
@ -243,6 +243,7 @@ conf.set_quoted('SYSTEMD_EXPORT_PATH', join_paths(rootlib
|
|||
conf.set_quoted('VENDOR_KEYRING_PATH', join_paths(rootlibexecdir, 'import-pubring.gpg'))
|
||||
conf.set_quoted('USER_KEYRING_PATH', join_paths(pkgsysconfdir, 'import-pubring.gpg'))
|
||||
conf.set_quoted('DOCUMENT_ROOT', join_paths(pkgdatadir, 'gatewayd'))
|
||||
conf.set_quoted('SYSTEMD_HOMEWORK_PATH', join_paths(rootlibexecdir, 'systemd-homework'))
|
||||
conf.set_quoted('SYSTEMD_USERWORK_PATH', join_paths(rootlibexecdir, 'systemd-userwork'))
|
||||
conf.set10('MEMORY_ACCOUNTING_DEFAULT', memory_accounting_default)
|
||||
conf.set_quoted('MEMORY_ACCOUNTING_DEFAULT_YES_NO', memory_accounting_default ? 'yes' : 'no')
|
||||
|
@ -884,6 +885,16 @@ else
|
|||
endif
|
||||
conf.set10('HAVE_LIBFDISK', have)
|
||||
|
||||
want_pwquality = get_option('pwquality')
|
||||
if want_pwquality != 'false' and not skip_deps
|
||||
libpwquality = dependency('pwquality', required : want_pwquality == 'true')
|
||||
have = libpwquality.found()
|
||||
else
|
||||
have = false
|
||||
libpwquality = []
|
||||
endif
|
||||
conf.set10('HAVE_PWQUALITY', have)
|
||||
|
||||
want_seccomp = get_option('seccomp')
|
||||
if want_seccomp != 'false' and not skip_deps
|
||||
libseccomp = dependency('libseccomp',
|
||||
|
@ -1011,6 +1022,9 @@ if want_libcryptsetup != 'false' and not skip_deps
|
|||
version : '>= 2.0.1',
|
||||
required : want_libcryptsetup == 'true')
|
||||
have = libcryptsetup.found()
|
||||
|
||||
conf.set10('HAVE_CRYPT_SET_METADATA_SIZE',
|
||||
have and cc.has_function('crypt_set_metadata_size', dependencies : libcryptsetup))
|
||||
else
|
||||
have = false
|
||||
libcryptsetup = []
|
||||
|
@ -1316,6 +1330,19 @@ else
|
|||
endif
|
||||
conf.set10('ENABLE_IMPORTD', have)
|
||||
|
||||
want_homed = get_option('homed')
|
||||
if want_homed != 'false'
|
||||
have = (conf.get('HAVE_OPENSSL') == 1 and
|
||||
conf.get('HAVE_LIBFDISK') == 1 and
|
||||
conf.get('HAVE_LIBCRYPTSETUP') == 1)
|
||||
if want_homed == 'true' and not have
|
||||
error('homed support was requested, but dependencies are not available')
|
||||
endif
|
||||
else
|
||||
have = false
|
||||
endif
|
||||
conf.set10('ENABLE_HOMED', have)
|
||||
|
||||
want_remote = get_option('remote')
|
||||
if want_remote != 'false'
|
||||
have_deps = [conf.get('HAVE_MICROHTTPD') == 1,
|
||||
|
@ -1564,6 +1591,7 @@ subdir('src/locale')
|
|||
subdir('src/machine')
|
||||
subdir('src/portable')
|
||||
subdir('src/userdb')
|
||||
subdir('src/home')
|
||||
subdir('src/nspawn')
|
||||
subdir('src/resolve')
|
||||
subdir('src/timedate')
|
||||
|
@ -2034,6 +2062,68 @@ if conf.get('ENABLE_USERDB') == 1
|
|||
install_dir : rootbindir)
|
||||
endif
|
||||
|
||||
if conf.get('ENABLE_HOMED') == 1
|
||||
executable('systemd-homework',
|
||||
systemd_homework_sources,
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
dependencies : [threads,
|
||||
libcryptsetup,
|
||||
libblkid,
|
||||
libcrypt,
|
||||
libopenssl,
|
||||
libfdisk,
|
||||
libp11kit],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
|
||||
executable('systemd-homed',
|
||||
systemd_homed_sources,
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
dependencies : [threads,
|
||||
libcrypt,
|
||||
libopenssl,
|
||||
libpwquality],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
|
||||
executable('homectl',
|
||||
homectl_sources,
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
dependencies : [threads,
|
||||
libcrypt,
|
||||
libopenssl,
|
||||
libp11kit,
|
||||
libpwquality],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootbindir)
|
||||
|
||||
if conf.get('HAVE_PAM') == 1
|
||||
version_script_arg = join_paths(project_source_root, pam_systemd_home_sym)
|
||||
pam_systemd = shared_library(
|
||||
'pam_systemd_home',
|
||||
pam_systemd_home_c,
|
||||
name_prefix : '',
|
||||
include_directories : includes,
|
||||
link_args : ['-shared',
|
||||
'-Wl,--version-script=' + version_script_arg],
|
||||
link_with : [libsystemd_static,
|
||||
libshared_static],
|
||||
dependencies : [threads,
|
||||
libpam,
|
||||
libpam_misc,
|
||||
libcrypt],
|
||||
link_depends : pam_systemd_home_sym,
|
||||
install : true,
|
||||
install_dir : pamlibdir)
|
||||
endif
|
||||
endif
|
||||
|
||||
foreach alias : ['halt', 'poweroff', 'reboot', 'runlevel', 'shutdown', 'telinit']
|
||||
meson.add_install_script(meson_make_symlink,
|
||||
join_paths(rootbindir, 'systemctl'),
|
||||
|
@ -3291,6 +3381,8 @@ missing = []
|
|||
foreach tuple : [
|
||||
['libcryptsetup'],
|
||||
['PAM'],
|
||||
['pwquality'],
|
||||
['fdisk'],
|
||||
['p11kit'],
|
||||
['AUDIT'],
|
||||
['IMA'],
|
||||
|
@ -3329,6 +3421,7 @@ foreach tuple : [
|
|||
['machined'],
|
||||
['portabled'],
|
||||
['userdb'],
|
||||
['homed'],
|
||||
['importd'],
|
||||
['hostnamed'],
|
||||
['timedated'],
|
||||
|
|
|
@ -98,6 +98,8 @@ option('portabled', type : 'boolean',
|
|||
description : 'install the systemd-portabled stack')
|
||||
option('userdb', type : 'boolean',
|
||||
description : 'install the systemd-userdbd stack')
|
||||
option('homed', type : 'boolean',
|
||||
description : 'install the systemd-homed stack')
|
||||
option('networkd', type : 'boolean',
|
||||
description : 'install the systemd-networkd stack')
|
||||
option('timedated', type : 'boolean',
|
||||
|
@ -268,6 +270,8 @@ option('kmod', type : 'combo', choices : ['auto', 'true', 'false'],
|
|||
description : 'support for loadable modules')
|
||||
option('pam', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
description : 'PAM support')
|
||||
option('pwquality', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
description : 'libpwquality support')
|
||||
option('microhttpd', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
description : 'libµhttpd support')
|
||||
option('libcryptsetup', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
|
|
28
po/pl.po
28
po/pl.po
|
@ -6,8 +6,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: systemd\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/systemd/systemd/issues\n"
|
||||
"POT-Creation-Date: 2019-10-26 14:02+0000\n"
|
||||
"PO-Revision-Date: 2019-10-26 16:05+0200\n"
|
||||
"POT-Creation-Date: 2020-01-29 16:25+0000\n"
|
||||
"PO-Revision-Date: 2020-01-29 17:28+0100\n"
|
||||
"Last-Translator: Piotr Drąg <piotrdrag@gmail.com>\n"
|
||||
"Language-Team: Polish <trans-pl@lists.fedoraproject.org>\n"
|
||||
"Language: pl\n"
|
||||
|
@ -522,6 +522,14 @@ msgstr "Ustawienie komunikatu wall"
|
|||
msgid "Authentication is required to set a wall message"
|
||||
msgstr "Wymagane jest uwierzytelnienie, aby ustawić komunikat wall"
|
||||
|
||||
#: src/login/org.freedesktop.login1.policy:395
|
||||
msgid "Change Session"
|
||||
msgstr "Zmiana sesji"
|
||||
|
||||
#: src/login/org.freedesktop.login1.policy:396
|
||||
msgid "Authentication is required for changing the virtual terminal."
|
||||
msgstr "Wymagane jest uwierzytelnienie, aby zmienić terminal wirtualny."
|
||||
|
||||
#: src/machine/org.freedesktop.machine1.policy:22
|
||||
msgid "Log into a local container"
|
||||
msgstr "Logowanie do lokalnego kontenera"
|
||||
|
@ -830,25 +838,25 @@ msgstr ""
|
|||
"Wymagane jest uwierzytelnienie, aby kontrolować, czy włączyć synchronizację "
|
||||
"czasu przez sieć."
|
||||
|
||||
#: src/core/dbus-unit.c:354
|
||||
#: src/core/dbus-unit.c:355
|
||||
msgid "Authentication is required to start '$(unit)'."
|
||||
msgstr "Wymagane jest uwierzytelnienie, aby uruchomić jednostkę „$(unit)”."
|
||||
|
||||
#: src/core/dbus-unit.c:355
|
||||
#: src/core/dbus-unit.c:356
|
||||
msgid "Authentication is required to stop '$(unit)'."
|
||||
msgstr "Wymagane jest uwierzytelnienie, aby zatrzymać jednostkę „$(unit)”."
|
||||
|
||||
#: src/core/dbus-unit.c:356
|
||||
#: src/core/dbus-unit.c:357
|
||||
msgid "Authentication is required to reload '$(unit)'."
|
||||
msgstr ""
|
||||
"Wymagane jest uwierzytelnienie, aby ponownie wczytać jednostkę „$(unit)”."
|
||||
|
||||
#: src/core/dbus-unit.c:357 src/core/dbus-unit.c:358
|
||||
#: src/core/dbus-unit.c:358 src/core/dbus-unit.c:359
|
||||
msgid "Authentication is required to restart '$(unit)'."
|
||||
msgstr ""
|
||||
"Wymagane jest uwierzytelnienie, aby ponownie uruchomić jednostkę „$(unit)”."
|
||||
|
||||
#: src/core/dbus-unit.c:530
|
||||
#: src/core/dbus-unit.c:531
|
||||
msgid ""
|
||||
"Authentication is required to send a UNIX signal to the processes of "
|
||||
"'$(unit)'."
|
||||
|
@ -856,18 +864,18 @@ msgstr ""
|
|||
"Wymagane jest uwierzytelnienie, aby wysłać sygnał uniksowy do procesów "
|
||||
"jednostki „$(unit)”."
|
||||
|
||||
#: src/core/dbus-unit.c:561
|
||||
#: src/core/dbus-unit.c:562
|
||||
msgid "Authentication is required to reset the \"failed\" state of '$(unit)'."
|
||||
msgstr ""
|
||||
"Wymagane jest uwierzytelnienie, aby przywrócić stan „failed” (niepowodzenia) "
|
||||
"jednostki „$(unit)”."
|
||||
|
||||
#: src/core/dbus-unit.c:594
|
||||
#: src/core/dbus-unit.c:595
|
||||
msgid "Authentication is required to set properties on '$(unit)'."
|
||||
msgstr ""
|
||||
"Wymagane jest uwierzytelnienie, aby ustawić właściwości jednostki „$(unit)”."
|
||||
|
||||
#: src/core/dbus-unit.c:703
|
||||
#: src/core/dbus-unit.c:704
|
||||
msgid ""
|
||||
"Authentication is required to delete files and directories associated with "
|
||||
"'$(unit)'."
|
||||
|
|
|
@ -36,7 +36,7 @@ apt-get -q --allow-releaseinfo-change update
|
|||
apt-get -y dist-upgrade
|
||||
apt-get install -y eatmydata
|
||||
# The following four are needed as long as these deps are not covered by Debian's own packaging
|
||||
apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev
|
||||
apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev libpwquality-dev
|
||||
apt-get purge --auto-remove -y unattended-upgrades
|
||||
systemctl unmask systemd-networkd
|
||||
systemctl enable systemd-networkd
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "dns-domain.h"
|
||||
#include "errno-util.h"
|
||||
#include "home-util.h"
|
||||
#include "libcrypt-util.h"
|
||||
#include "memory-util.h"
|
||||
#include "path-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "user-util.h"
|
||||
|
||||
bool suitable_user_name(const char *name) {
|
||||
|
||||
/* Checks whether the specified name is suitable for management via homed. Note that our client side
|
||||
* usually validate susing a simple valid_user_group_name(), while server side we are a bit more
|
||||
* restrictive, so that we can change the rules server side without having to update things client
|
||||
* side, too. */
|
||||
|
||||
if (!valid_user_group_name(name))
|
||||
return false;
|
||||
|
||||
/* We generally rely on NSS to tell us which users not to care for, but let's filter out some
|
||||
* particularly well-known users. */
|
||||
if (STR_IN_SET(name,
|
||||
"root",
|
||||
"nobody",
|
||||
NOBODY_USER_NAME, NOBODY_GROUP_NAME))
|
||||
return false;
|
||||
|
||||
/* Let's also defend our own namespace, as well as Debian's (unwritten?) logic of prefixing system
|
||||
* users with underscores. */
|
||||
if (STARTSWITH_SET(name, "systemd-", "_"))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int suitable_realm(const char *realm) {
|
||||
_cleanup_free_ char *normalized = NULL;
|
||||
int r;
|
||||
|
||||
/* Similar to the above: let's validate the realm a bit stricter server-side than client side */
|
||||
|
||||
r = dns_name_normalize(realm, 0, &normalized); /* this also checks general validity */
|
||||
if (r == -EINVAL)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!streq(realm, normalized)) /* is this normalized? */
|
||||
return false;
|
||||
|
||||
if (dns_name_is_root(realm)) /* Don't allow top level domain */
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int suitable_image_path(const char *path) {
|
||||
|
||||
return !empty_or_root(path) &&
|
||||
path_is_valid(path) &&
|
||||
path_is_absolute(path);
|
||||
}
|
||||
|
||||
int split_user_name_realm(const char *t, char **ret_user_name, char **ret_realm) {
|
||||
_cleanup_free_ char *user_name = NULL, *realm = NULL;
|
||||
const char *c;
|
||||
int r;
|
||||
|
||||
assert(t);
|
||||
assert(ret_user_name);
|
||||
assert(ret_realm);
|
||||
|
||||
c = strchr(t, '@');
|
||||
if (!c) {
|
||||
user_name = strdup(t);
|
||||
if (!user_name)
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
user_name = strndup(t, c - t);
|
||||
if (!user_name)
|
||||
return -ENOMEM;
|
||||
|
||||
realm = strdup(c + 1);
|
||||
if (!realm)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (!suitable_user_name(user_name))
|
||||
return -EINVAL;
|
||||
|
||||
if (realm) {
|
||||
r = suitable_realm(realm);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*ret_user_name = TAKE_PTR(user_name);
|
||||
*ret_realm = TAKE_PTR(realm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bus_message_append_secret(sd_bus_message *m, UserRecord *secret) {
|
||||
_cleanup_(erase_and_freep) char *formatted = NULL;
|
||||
JsonVariant *v;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(secret);
|
||||
|
||||
if (!FLAGS_SET(secret->mask, USER_RECORD_SECRET))
|
||||
return sd_bus_message_append(m, "s", "{}");
|
||||
|
||||
v = json_variant_by_key(secret->json, "secret");
|
||||
if (!v)
|
||||
return -EINVAL;
|
||||
|
||||
r = json_variant_format(v, 0, &formatted);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_bus_message_append(m, "s", formatted);
|
||||
}
|
||||
|
||||
int test_password_one(const char *hashed_password, const char *password) {
|
||||
struct crypt_data cc = {};
|
||||
const char *k;
|
||||
bool b;
|
||||
|
||||
errno = 0;
|
||||
k = crypt_r(password, hashed_password, &cc);
|
||||
if (!k) {
|
||||
explicit_bzero_safe(&cc, sizeof(cc));
|
||||
return errno_or_else(EINVAL);
|
||||
}
|
||||
|
||||
b = streq(k, hashed_password);
|
||||
explicit_bzero_safe(&cc, sizeof(cc));
|
||||
return b;
|
||||
}
|
||||
|
||||
int test_password_many(char **hashed_password, const char *password) {
|
||||
char **hpw;
|
||||
int r;
|
||||
|
||||
STRV_FOREACH(hpw, hashed_password) {
|
||||
r = test_password_one(*hpw, password);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "time-util.h"
|
||||
#include "user-record.h"
|
||||
|
||||
bool suitable_user_name(const char *name);
|
||||
int suitable_realm(const char *realm);
|
||||
int suitable_image_path(const char *path);
|
||||
|
||||
int split_user_name_realm(const char *t, char **ret_user_name, char **ret_realm);
|
||||
|
||||
int bus_message_append_secret(sd_bus_message *m, UserRecord *secret);
|
||||
|
||||
/* Many of our operations might be slow due to crypto, fsck, recursive chown() and so on. For these
|
||||
* operations permit a *very* long time-out */
|
||||
#define HOME_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
|
||||
|
||||
int test_password_one(const char *hashed_password, const char *password);
|
||||
int test_password_many(char **hashed_password, const char *password);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,64 @@
|
|||
#include "homed-bus.h"
|
||||
#include "strv.h"
|
||||
|
||||
int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *error) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *full = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
unsigned line = 0, column = 0;
|
||||
const char *json;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
r = sd_bus_message_read(m, "s", &json);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = json_parse(json, JSON_PARSE_SENSITIVE, &v, &line, &column);
|
||||
if (r < 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse JSON secret record at %u:%u: %m", line, column);
|
||||
|
||||
r = json_build(&full, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("secret", JSON_BUILD_VARIANT(v))));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
hr = user_record_new();
|
||||
if (!hr)
|
||||
return -ENOMEM;
|
||||
|
||||
r = user_record_load(hr, full, USER_RECORD_REQUIRE_SECRET);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(hr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, UserRecord **ret, sd_bus_error *error) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
unsigned line = 0, column = 0;
|
||||
const char *json;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
r = sd_bus_message_read(m, "s", &json);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = json_parse(json, JSON_PARSE_SENSITIVE, &v, &line, &column);
|
||||
if (r < 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse JSON identity record at %u:%u: %m", line, column);
|
||||
|
||||
hr = user_record_new();
|
||||
if (!hr)
|
||||
return -ENOMEM;
|
||||
|
||||
r = user_record_load(hr, v, flags);
|
||||
if (r < 0)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "JSON data is not a valid identity record");
|
||||
|
||||
*ret = TAKE_PTR(hr);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "user-record.h"
|
||||
#include "json.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);
|
|
@ -0,0 +1,877 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <linux/capability.h>
|
||||
|
||||
#include "bus-common-errors.h"
|
||||
#include "bus-polkit.h"
|
||||
#include "fd-util.h"
|
||||
#include "homed-bus.h"
|
||||
#include "homed-home-bus.h"
|
||||
#include "homed-home.h"
|
||||
#include "strv.h"
|
||||
#include "user-record-util.h"
|
||||
#include "user-util.h"
|
||||
|
||||
static int property_get_unix_record(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
Home *h = userdata;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
assert(h);
|
||||
|
||||
return sd_bus_message_append(
|
||||
reply, "(suusss)",
|
||||
h->user_name,
|
||||
(uint32_t) h->uid,
|
||||
h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID,
|
||||
h->record ? user_record_real_name(h->record) : NULL,
|
||||
h->record ? user_record_home_directory(h->record) : NULL,
|
||||
h->record ? user_record_shell(h->record) : NULL);
|
||||
}
|
||||
|
||||
static int property_get_state(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
Home *h = userdata;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
assert(h);
|
||||
|
||||
return sd_bus_message_append(reply, "s", home_state_to_string(home_get_state(h)));
|
||||
}
|
||||
|
||||
int bus_home_client_is_trusted(Home *h, sd_bus_message *message) {
|
||||
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
|
||||
uid_t euid;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
|
||||
if (!message)
|
||||
return -EINVAL;
|
||||
|
||||
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_creds_get_euid(creds, &euid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return euid == 0 || h->uid == euid;
|
||||
}
|
||||
|
||||
int bus_home_get_record_json(
|
||||
Home *h,
|
||||
sd_bus_message *message,
|
||||
char **ret,
|
||||
bool *ret_incomplete) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *augmented = NULL;
|
||||
UserRecordLoadFlags flags;
|
||||
int r, trusted;
|
||||
|
||||
assert(h);
|
||||
assert(ret);
|
||||
|
||||
trusted = bus_home_client_is_trusted(h, message);
|
||||
if (trusted < 0) {
|
||||
log_warning_errno(trusted, "Failed to determine whether client is trusted, assuming untrusted.");
|
||||
trusted = false;
|
||||
}
|
||||
|
||||
flags = USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_BINDING|USER_RECORD_STRIP_SECRET|USER_RECORD_ALLOW_STATUS|USER_RECORD_ALLOW_SIGNATURE;
|
||||
if (trusted)
|
||||
flags |= USER_RECORD_ALLOW_PRIVILEGED;
|
||||
else
|
||||
flags |= USER_RECORD_STRIP_PRIVILEGED;
|
||||
|
||||
r = home_augment_status(h, flags, &augmented);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = json_variant_format(augmented->json, 0, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (ret_incomplete)
|
||||
*ret_incomplete = augmented->incomplete;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int property_get_user_record(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_free_ char *json = NULL;
|
||||
Home *h = userdata;
|
||||
bool incomplete;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
assert(h);
|
||||
|
||||
r = bus_home_get_record_json(h, sd_bus_get_current_message(bus), &json, &incomplete);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_bus_message_append(reply, "(sb)", json, incomplete);
|
||||
}
|
||||
|
||||
int bus_home_method_activate(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *secret = NULL;
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = bus_message_read_secret(message, &secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_activate(h, secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
assert(!h->current_operation);
|
||||
|
||||
/* The operation is now in process, keep track of this message so that we can later reply to it. */
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_deactivate(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = home_deactivate(h, false, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
assert(!h->current_operation);
|
||||
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_unregister(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
CAP_SYS_ADMIN,
|
||||
"org.freedesktop.home1.remove-home",
|
||||
NULL,
|
||||
true,
|
||||
UID_INVALID,
|
||||
&h->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = home_unregister(h, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r > 0);
|
||||
|
||||
/* Note that home_unregister() destroyed 'h' here, so no more accesses */
|
||||
|
||||
return sd_bus_reply_method_return(message, NULL);
|
||||
}
|
||||
|
||||
int bus_home_method_realize(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *secret = NULL;
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = bus_message_read_secret(message, &secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
CAP_SYS_ADMIN,
|
||||
"org.freedesktop.home1.create-home",
|
||||
NULL,
|
||||
true,
|
||||
UID_INVALID,
|
||||
&h->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = home_create(h, secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
assert(!h->current_operation);
|
||||
|
||||
h->unregister_on_failure = false;
|
||||
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_remove(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
CAP_SYS_ADMIN,
|
||||
"org.freedesktop.home1.remove-home",
|
||||
NULL,
|
||||
true,
|
||||
UID_INVALID,
|
||||
&h->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = home_remove(h, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) /* Done already. Note that home_remove() destroyed 'h' here, so no more accesses */
|
||||
return sd_bus_reply_method_return(message, NULL);
|
||||
|
||||
assert(!h->current_operation);
|
||||
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_fixate(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *secret = NULL;
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = bus_message_read_secret(message, &secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_fixate(h, secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
assert(!h->current_operation);
|
||||
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_authenticate(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *secret = NULL;
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = bus_message_read_secret(message, &secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
CAP_SYS_ADMIN,
|
||||
"org.freedesktop.home1.authenticate-home",
|
||||
NULL,
|
||||
true,
|
||||
h->uid,
|
||||
&h->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = home_authenticate(h, secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
assert(!h->current_operation);
|
||||
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *hr, sd_bus_error *error) {
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(message);
|
||||
assert(hr);
|
||||
|
||||
r = user_record_is_supported(hr, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
CAP_SYS_ADMIN,
|
||||
"org.freedesktop.home1.update-home",
|
||||
NULL,
|
||||
true,
|
||||
UID_INVALID,
|
||||
&h->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = home_update(h, hr, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
assert(!h->current_operation);
|
||||
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_update(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_REQUIRE_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE, &hr, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return bus_home_method_update_record(h, message, hr, error);
|
||||
}
|
||||
|
||||
int bus_home_method_resize(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *secret = NULL;
|
||||
Home *h = userdata;
|
||||
uint64_t sz;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = sd_bus_message_read(message, "t", &sz);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bus_message_read_secret(message, &secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
CAP_SYS_ADMIN,
|
||||
"org.freedesktop.home1.resize-home",
|
||||
NULL,
|
||||
true,
|
||||
UID_INVALID,
|
||||
&h->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = home_resize(h, sz, secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
assert(!h->current_operation);
|
||||
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_change_password(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_secret = NULL, *old_secret = NULL;
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = bus_message_read_secret(message, &new_secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bus_message_read_secret(message, &old_secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
CAP_SYS_ADMIN,
|
||||
"org.freedesktop.home1.passwd-home",
|
||||
NULL,
|
||||
true,
|
||||
h->uid,
|
||||
&h->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = home_passwd(h, new_secret, old_secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
assert(!h->current_operation);
|
||||
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_lock(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = home_lock(h, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) /* Done */
|
||||
return sd_bus_reply_method_return(message, NULL);
|
||||
|
||||
/* The operation is now in process, keep track of this message so that we can later reply to it. */
|
||||
assert(!h->current_operation);
|
||||
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_unlock(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *secret = NULL;
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = bus_message_read_secret(message, &secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_unlock(h, secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
assert(!h->current_operation);
|
||||
|
||||
/* The operation is now in process, keep track of this message so that we can later reply to it. */
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_acquire(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *secret = NULL;
|
||||
_cleanup_(operation_unrefp) Operation *o = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
int r, please_suspend;
|
||||
Home *h = userdata;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = bus_message_read_secret(message, &secret, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_read(message, "b", &please_suspend);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* This operation might not be something we can executed immediately, hence queue it */
|
||||
fd = home_create_fifo(h, please_suspend);
|
||||
if (fd < 0)
|
||||
return sd_bus_reply_method_errnof(message, fd, "Failed to allocate fifo for %s: %m", h->user_name);
|
||||
|
||||
o = operation_new(OPERATION_ACQUIRE, message);
|
||||
if (!o)
|
||||
return -ENOMEM;
|
||||
|
||||
o->secret = TAKE_PTR(secret);
|
||||
o->send_fd = TAKE_FD(fd);
|
||||
|
||||
r = home_schedule_operation(h, o, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_method_ref(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_close_ int fd = -1;
|
||||
Home *h = userdata;
|
||||
HomeState state;
|
||||
int please_suspend, r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
r = sd_bus_message_read(message, "b", &please_suspend);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
state = home_get_state(h);
|
||||
switch (state) {
|
||||
case HOME_ABSENT:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
|
||||
case HOME_UNFIXATED:
|
||||
case HOME_INACTIVE:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s not active.", h->user_name);
|
||||
case HOME_LOCKED:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
|
||||
default:
|
||||
if (HOME_STATE_IS_ACTIVE(state))
|
||||
break;
|
||||
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
|
||||
}
|
||||
|
||||
fd = home_create_fifo(h, please_suspend);
|
||||
if (fd < 0)
|
||||
return sd_bus_reply_method_errnof(message, fd, "Failed to allocate fifo for %s: %m", h->user_name);
|
||||
|
||||
return sd_bus_reply_method_return(message, "h", fd);
|
||||
}
|
||||
|
||||
int bus_home_method_release(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(operation_unrefp) Operation *o = NULL;
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(h);
|
||||
|
||||
o = operation_new(OPERATION_RELEASE, message);
|
||||
if (!o)
|
||||
return -ENOMEM;
|
||||
|
||||
r = home_schedule_operation(h, o, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* We map a uid_t as uint32_t bus property, let's ensure this is safe. */
|
||||
assert_cc(sizeof(uid_t) == sizeof(uint32_t));
|
||||
|
||||
const sd_bus_vtable home_vtable[] = {
|
||||
SD_BUS_VTABLE_START(0),
|
||||
SD_BUS_PROPERTY("UserName", "s", NULL, offsetof(Home, user_name), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("UID", "u", NULL, offsetof(Home, uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("UnixRecord", "(suusss)", property_get_unix_record, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0),
|
||||
SD_BUS_PROPERTY("UserRecord", "(sb)", property_get_user_record, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("Activate", "s", NULL, bus_home_method_activate, SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("Deactivate", NULL, NULL, bus_home_method_deactivate, 0),
|
||||
SD_BUS_METHOD("Unregister", NULL, NULL, bus_home_method_unregister, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("Realize", "s", NULL, bus_home_method_realize, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("Remove", NULL, NULL, bus_home_method_remove, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("Fixate", "s", NULL, bus_home_method_fixate, SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("Authenticate", "s", NULL, bus_home_method_authenticate, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("Update", "s", NULL, bus_home_method_update, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("Resize", "ts", NULL, bus_home_method_resize, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("ChangePassword", "ss", NULL, bus_home_method_change_password, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("Lock", NULL, NULL, bus_home_method_lock, 0),
|
||||
SD_BUS_METHOD("Unlock", "s", NULL, bus_home_method_unlock, SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("Acquire", "sb", "h", bus_home_method_acquire, SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("Ref", "b", "h", bus_home_method_ref, 0),
|
||||
SD_BUS_METHOD("Release", NULL, NULL, bus_home_method_release, 0),
|
||||
SD_BUS_VTABLE_END
|
||||
};
|
||||
|
||||
int bus_home_path(Home *h, char **ret) {
|
||||
assert(ret);
|
||||
|
||||
return sd_bus_path_encode("/org/freedesktop/home1/home", h->user_name, ret);
|
||||
}
|
||||
|
||||
int bus_home_object_find(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
void *userdata,
|
||||
void **found,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_free_ char *e = NULL;
|
||||
Manager *m = userdata;
|
||||
uid_t uid;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
r = sd_bus_path_decode(path, "/org/freedesktop/home1/home", &e);
|
||||
if (r <= 0)
|
||||
return 0;
|
||||
|
||||
if (parse_uid(e, &uid) >= 0)
|
||||
h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid));
|
||||
else
|
||||
h = hashmap_get(m->homes_by_name, e);
|
||||
if (!h)
|
||||
return 0;
|
||||
|
||||
*found = h;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_node_enumerator(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
void *userdata,
|
||||
char ***nodes,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
Manager *m = userdata;
|
||||
size_t k = 0;
|
||||
Iterator i;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(nodes);
|
||||
|
||||
l = new0(char*, hashmap_size(m->homes_by_uid) + 1);
|
||||
if (!l)
|
||||
return -ENOMEM;
|
||||
|
||||
HASHMAP_FOREACH(h, m->homes_by_uid, i) {
|
||||
r = bus_home_path(h, l + k);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
*nodes = TAKE_PTR(l);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int on_deferred_change(sd_event_source *s, void *userdata) {
|
||||
_cleanup_free_ char *path = NULL;
|
||||
Home *h = userdata;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
|
||||
h->deferred_change_event_source = sd_event_source_unref(h->deferred_change_event_source);
|
||||
|
||||
r = bus_home_path(h, &path);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to generate home bus path, ignoring: %m");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (h->announced)
|
||||
r = sd_bus_emit_properties_changed_strv(h->manager->bus, path, "org.freedesktop.home1.Home", NULL);
|
||||
else
|
||||
r = sd_bus_emit_object_added(h->manager->bus, path);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to send home change event, ignoring: %m");
|
||||
else
|
||||
h->announced = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bus_home_emit_change(Home *h) {
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
|
||||
if (h->deferred_change_event_source)
|
||||
return 1;
|
||||
|
||||
if (!h->manager->event)
|
||||
return 0;
|
||||
|
||||
if (IN_SET(sd_event_get_state(h->manager->event), SD_EVENT_FINISHED, SD_EVENT_EXITING))
|
||||
return 0;
|
||||
|
||||
r = sd_event_add_defer(h->manager->event, &h->deferred_change_event_source, on_deferred_change, h);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate deferred change event source: %m");
|
||||
|
||||
r = sd_event_source_set_priority(h->deferred_change_event_source, SD_EVENT_PRIORITY_IDLE+5);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to tweak priority of event source, ignoring: %m");
|
||||
|
||||
(void) sd_event_source_set_description(h->deferred_change_event_source, "deferred-change-event");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bus_home_emit_remove(Home *h) {
|
||||
_cleanup_free_ char *path = NULL;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
|
||||
if (!h->announced)
|
||||
return 0;
|
||||
|
||||
r = bus_home_path(h, &path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_emit_object_removed(h->manager->bus, path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
h->announced = false;
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "homed-home.h"
|
||||
|
||||
int bus_home_client_is_trusted(Home *h, sd_bus_message *message);
|
||||
int bus_home_get_record_json(Home *h, sd_bus_message *message, char **ret, bool *ret_incomplete);
|
||||
|
||||
int bus_home_method_activate(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_deactivate(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_unregister(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_realize(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *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_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);
|
||||
int bus_home_method_unlock(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_acquire(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_home_method_release(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
|
||||
extern const sd_bus_vtable home_vtable[];
|
||||
|
||||
int bus_home_path(Home *h, char **ret);
|
||||
|
||||
int bus_home_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error);
|
||||
int bus_home_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);
|
||||
|
||||
int bus_home_emit_change(Home *h);
|
||||
int bus_home_emit_remove(Home *h);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,168 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
typedef struct Home Home;
|
||||
|
||||
#include "homed-manager.h"
|
||||
#include "homed-operation.h"
|
||||
#include "list.h"
|
||||
#include "ordered-set.h"
|
||||
#include "user-record.h"
|
||||
|
||||
typedef enum HomeState {
|
||||
HOME_UNFIXATED, /* home exists, but local record does not */
|
||||
HOME_ABSENT, /* local record exists, but home does not */
|
||||
HOME_INACTIVE, /* record and home exist, but is not logged in */
|
||||
HOME_FIXATING, /* generating local record from home */
|
||||
HOME_FIXATING_FOR_ACTIVATION, /* fixating in order to activate soon */
|
||||
HOME_FIXATING_FOR_ACQUIRE, /* fixating because Acquire() was called */
|
||||
HOME_ACTIVATING,
|
||||
HOME_ACTIVATING_FOR_ACQUIRE, /* activating because Acquire() was called */
|
||||
HOME_DEACTIVATING,
|
||||
HOME_ACTIVE, /* logged in right now */
|
||||
HOME_LOCKING,
|
||||
HOME_LOCKED,
|
||||
HOME_UNLOCKING,
|
||||
HOME_UNLOCKING_FOR_ACQUIRE, /* unlocking because Acquire() was called */
|
||||
HOME_CREATING,
|
||||
HOME_REMOVING,
|
||||
HOME_UPDATING,
|
||||
HOME_UPDATING_WHILE_ACTIVE,
|
||||
HOME_RESIZING,
|
||||
HOME_RESIZING_WHILE_ACTIVE,
|
||||
HOME_PASSWD,
|
||||
HOME_PASSWD_WHILE_ACTIVE,
|
||||
HOME_AUTHENTICATING,
|
||||
HOME_AUTHENTICATING_WHILE_ACTIVE,
|
||||
HOME_AUTHENTICATING_FOR_ACQUIRE, /* authenticating because Acquire() was called */
|
||||
_HOME_STATE_MAX,
|
||||
_HOME_STATE_INVALID = -1
|
||||
} HomeState;
|
||||
|
||||
static inline bool HOME_STATE_IS_ACTIVE(HomeState state) {
|
||||
return IN_SET(state,
|
||||
HOME_ACTIVE,
|
||||
HOME_UPDATING_WHILE_ACTIVE,
|
||||
HOME_RESIZING_WHILE_ACTIVE,
|
||||
HOME_PASSWD_WHILE_ACTIVE,
|
||||
HOME_AUTHENTICATING_WHILE_ACTIVE,
|
||||
HOME_AUTHENTICATING_FOR_ACQUIRE);
|
||||
}
|
||||
|
||||
static inline bool HOME_STATE_IS_EXECUTING_OPERATION(HomeState state) {
|
||||
return IN_SET(state,
|
||||
HOME_FIXATING,
|
||||
HOME_FIXATING_FOR_ACTIVATION,
|
||||
HOME_FIXATING_FOR_ACQUIRE,
|
||||
HOME_ACTIVATING,
|
||||
HOME_ACTIVATING_FOR_ACQUIRE,
|
||||
HOME_DEACTIVATING,
|
||||
HOME_LOCKING,
|
||||
HOME_UNLOCKING,
|
||||
HOME_UNLOCKING_FOR_ACQUIRE,
|
||||
HOME_CREATING,
|
||||
HOME_REMOVING,
|
||||
HOME_UPDATING,
|
||||
HOME_UPDATING_WHILE_ACTIVE,
|
||||
HOME_RESIZING,
|
||||
HOME_RESIZING_WHILE_ACTIVE,
|
||||
HOME_PASSWD,
|
||||
HOME_PASSWD_WHILE_ACTIVE,
|
||||
HOME_AUTHENTICATING,
|
||||
HOME_AUTHENTICATING_WHILE_ACTIVE,
|
||||
HOME_AUTHENTICATING_FOR_ACQUIRE);
|
||||
}
|
||||
|
||||
struct Home {
|
||||
Manager *manager;
|
||||
char *user_name;
|
||||
uid_t uid;
|
||||
|
||||
char *sysfs; /* When found via plugged in device, the sysfs path to it */
|
||||
|
||||
/* Note that the 'state' field is only set to a state while we are doing something (i.e. activating,
|
||||
* deactivating, creating, removing, and such), or when the home is an "unfixated" one. When we are
|
||||
* done with an operation we invalidate the state. This is hint for home_get_state() to check the
|
||||
* state on request as needed from the mount table and similar.*/
|
||||
HomeState state;
|
||||
int signed_locally; /* signed only by us */
|
||||
|
||||
UserRecord *record;
|
||||
|
||||
pid_t worker_pid;
|
||||
int worker_stdout_fd;
|
||||
sd_event_source *worker_event_source;
|
||||
int worker_error_code;
|
||||
|
||||
/* The message we are currently processing, and thus need to reply to on completion */
|
||||
Operation *current_operation;
|
||||
|
||||
/* Stores the raw, plaintext passwords, but only for short periods of time */
|
||||
UserRecord *secret;
|
||||
|
||||
/* When we create a home and that fails, we should possibly unregister the record altogether
|
||||
* again, which is remembered in this boolean. */
|
||||
bool unregister_on_failure;
|
||||
|
||||
/* The reading side of a FIFO stored in /run/systemd/home/, the writing side being used for reference
|
||||
* counting. The references dropped to zero as soon as we see EOF. This concept exists twice: once
|
||||
* for clients that are fine if we suspend the home directory on system suspend, and once for cliets
|
||||
* that are not ok with that. This allows us to determine for each home whether there are any clients
|
||||
* that support unsuspend. */
|
||||
sd_event_source *ref_event_source_please_suspend;
|
||||
sd_event_source *ref_event_source_dont_suspend;
|
||||
|
||||
/* Any pending operations we still need to execute. These are for operations we want to queue if we
|
||||
* can't execute them right-away. */
|
||||
OrderedSet *pending_operations;
|
||||
|
||||
/* A defer event source that processes pending acquire/release/eof events. We have a common
|
||||
* dispatcher that processes all three kinds of events. */
|
||||
sd_event_source *pending_event_source;
|
||||
|
||||
/* Did we send out a D-Bus notification about this entry? */
|
||||
bool announced;
|
||||
|
||||
/* Used to coalesce bus PropertiesChanged events */
|
||||
sd_event_source *deferred_change_event_source;
|
||||
};
|
||||
|
||||
int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret);
|
||||
Home *home_free(Home *h);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Home*, home_free);
|
||||
|
||||
int home_set_record(Home *h, UserRecord *hr);
|
||||
int home_save_record(Home *h);
|
||||
int home_unlink_record(Home *h);
|
||||
|
||||
int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error);
|
||||
int home_activate(Home *h, 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_remove(Home *h, sd_bus_error *error);
|
||||
int home_update(Home *h, UserRecord *new_record, sd_bus_error *error);
|
||||
int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, 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);
|
||||
int home_lock(Home *h, sd_bus_error *error);
|
||||
int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error);
|
||||
|
||||
HomeState home_get_state(Home *h);
|
||||
|
||||
void home_process_notify(Home *h, char **l);
|
||||
|
||||
int home_killall(Home *h);
|
||||
|
||||
int home_augment_status(Home *h, UserRecordLoadFlags flags, UserRecord **ret);
|
||||
|
||||
int home_create_fifo(Home *h, bool please_suspend);
|
||||
int home_schedule_operation(Home *h, Operation *o, sd_bus_error *error);
|
||||
|
||||
int home_auto_login(Home *h, char ***ret_seats);
|
||||
|
||||
int home_set_current_message(Home *h, sd_bus_message *m);
|
||||
|
||||
const char *home_state_to_string(HomeState state);
|
||||
HomeState home_state_from_string(const char *s);
|
|
@ -0,0 +1,690 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <linux/capability.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "bus-common-errors.h"
|
||||
#include "bus-polkit.h"
|
||||
#include "format-util.h"
|
||||
#include "homed-bus.h"
|
||||
#include "homed-home-bus.h"
|
||||
#include "homed-manager-bus.h"
|
||||
#include "homed-manager.h"
|
||||
#include "strv.h"
|
||||
#include "user-record-sign.h"
|
||||
#include "user-record-util.h"
|
||||
#include "user-util.h"
|
||||
|
||||
static int property_get_auto_login(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
Manager *m = userdata;
|
||||
Iterator i;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
assert(m);
|
||||
|
||||
r = sd_bus_message_open_container(reply, 'a', "(sso)");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
HASHMAP_FOREACH(h, m->homes_by_name, i) {
|
||||
_cleanup_(strv_freep) char **seats = NULL;
|
||||
_cleanup_free_ char *home_path = NULL;
|
||||
char **s;
|
||||
|
||||
r = home_auto_login(h, &seats);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to determine whether home '%s' is candidate for auto-login, ignoring: %m", h->user_name);
|
||||
continue;
|
||||
}
|
||||
if (!r)
|
||||
continue;
|
||||
|
||||
r = bus_home_path(h, &home_path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate home bus path: %m");
|
||||
|
||||
STRV_FOREACH(s, seats) {
|
||||
r = sd_bus_message_append(reply, "(sso)", h->user_name, *s, home_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return sd_bus_message_close_container(reply);
|
||||
}
|
||||
|
||||
static int method_get_home_by_name(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_free_ char *path = NULL;
|
||||
const char *user_name;
|
||||
Manager *m = userdata;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(m);
|
||||
|
||||
r = sd_bus_message_read(message, "s", &user_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!valid_user_group_name(user_name))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
|
||||
|
||||
h = hashmap_get(m->homes_by_name, user_name);
|
||||
if (!h)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
|
||||
|
||||
r = bus_home_path(h, &path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_bus_reply_method_return(
|
||||
message, "usussso",
|
||||
(uint32_t) h->uid,
|
||||
home_state_to_string(home_get_state(h)),
|
||||
h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID,
|
||||
h->record ? user_record_real_name(h->record) : NULL,
|
||||
h->record ? user_record_home_directory(h->record) : NULL,
|
||||
h->record ? user_record_shell(h->record) : NULL,
|
||||
path);
|
||||
}
|
||||
|
||||
static int method_get_home_by_uid(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_free_ char *path = NULL;
|
||||
Manager *m = userdata;
|
||||
uint32_t uid;
|
||||
int r;
|
||||
Home *h;
|
||||
|
||||
assert(message);
|
||||
assert(m);
|
||||
|
||||
r = sd_bus_message_read(message, "u", &uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!uid_is_valid(uid))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "UID " UID_FMT " is not valid", uid);
|
||||
|
||||
h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid));
|
||||
if (!h)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for UID " UID_FMT " known", uid);
|
||||
|
||||
/* Note that we don't use bus_home_path() here, but build the path manually, since if we are queried
|
||||
* for a UID we should also generate the bus path with a UID, and bus_home_path() uses our more
|
||||
* typical bus path by name. */
|
||||
if (asprintf(&path, "/org/freedesktop/home1/home/" UID_FMT, h->uid) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
return sd_bus_reply_method_return(
|
||||
message, "ssussso",
|
||||
h->user_name,
|
||||
home_state_to_string(home_get_state(h)),
|
||||
h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID,
|
||||
h->record ? user_record_real_name(h->record) : NULL,
|
||||
h->record ? user_record_home_directory(h->record) : NULL,
|
||||
h->record ? user_record_shell(h->record) : NULL,
|
||||
path);
|
||||
}
|
||||
|
||||
static int method_list_homes(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
Manager *m = userdata;
|
||||
Iterator i;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(m);
|
||||
|
||||
r = sd_bus_message_new_method_return(message, &reply);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_open_container(reply, 'a', "(susussso)");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
HASHMAP_FOREACH(h, m->homes_by_uid, i) {
|
||||
_cleanup_free_ char *path = NULL;
|
||||
|
||||
r = bus_home_path(h, &path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_append(
|
||||
reply, "(susussso)",
|
||||
h->user_name,
|
||||
(uint32_t) h->uid,
|
||||
home_state_to_string(home_get_state(h)),
|
||||
h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID,
|
||||
h->record ? user_record_real_name(h->record) : NULL,
|
||||
h->record ? user_record_home_directory(h->record) : NULL,
|
||||
h->record ? user_record_shell(h->record) : NULL,
|
||||
path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = sd_bus_message_close_container(reply);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_bus_send(NULL, reply, NULL);
|
||||
}
|
||||
|
||||
static int method_get_user_record_by_name(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_free_ char *json = NULL, *path = NULL;
|
||||
Manager *m = userdata;
|
||||
const char *user_name;
|
||||
bool incomplete;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(m);
|
||||
|
||||
r = sd_bus_message_read(message, "s", &user_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!valid_user_group_name(user_name))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
|
||||
|
||||
h = hashmap_get(m->homes_by_name, user_name);
|
||||
if (!h)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
|
||||
|
||||
r = bus_home_get_record_json(h, message, &json, &incomplete);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bus_home_path(h, &path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_bus_reply_method_return(
|
||||
message, "sbo",
|
||||
json,
|
||||
incomplete,
|
||||
path);
|
||||
}
|
||||
|
||||
static int method_get_user_record_by_uid(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_free_ char *json = NULL, *path = NULL;
|
||||
Manager *m = userdata;
|
||||
bool incomplete;
|
||||
uint32_t uid;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(m);
|
||||
|
||||
r = sd_bus_message_read(message, "u", &uid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!uid_is_valid(uid))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "UID " UID_FMT " is not valid", uid);
|
||||
|
||||
h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid));
|
||||
if (!h)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for UID " UID_FMT " known", uid);
|
||||
|
||||
r = bus_home_get_record_json(h, message, &json, &incomplete);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (asprintf(&path, "/org/freedesktop/home1/home/" UID_FMT, h->uid) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
return sd_bus_reply_method_return(
|
||||
message, "sbo",
|
||||
json,
|
||||
incomplete,
|
||||
path);
|
||||
}
|
||||
|
||||
static int generic_home_method(
|
||||
Manager *m,
|
||||
sd_bus_message *message,
|
||||
sd_bus_message_handler_t handler,
|
||||
sd_bus_error *error) {
|
||||
|
||||
const char *user_name;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
r = sd_bus_message_read(message, "s", &user_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!valid_user_group_name(user_name))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
|
||||
|
||||
h = hashmap_get(m->homes_by_name, user_name);
|
||||
if (!h)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
|
||||
|
||||
return handler(message, h, error);
|
||||
}
|
||||
|
||||
static int method_activate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_activate, error);
|
||||
}
|
||||
|
||||
static int method_deactivate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
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) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL;
|
||||
struct passwd *pw;
|
||||
struct group *gr;
|
||||
bool signed_locally;
|
||||
Home *other;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(hr);
|
||||
assert(ret);
|
||||
|
||||
r = user_record_is_supported(hr, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
other = hashmap_get(m->homes_by_name, hr->user_name);
|
||||
if (other)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists already, refusing.", hr->user_name);
|
||||
|
||||
pw = getpwnam(hr->user_name);
|
||||
if (pw)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists in the NSS user database, refusing.", hr->user_name);
|
||||
|
||||
gr = getgrnam(hr->user_name);
|
||||
if (gr)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s conflicts with an NSS group by the same name, refusing.", hr->user_name);
|
||||
|
||||
r = manager_verify_user_record(m, hr);
|
||||
switch (r) {
|
||||
|
||||
case USER_RECORD_UNSIGNED:
|
||||
/* If the record is unsigned, then let's sign it with our own key */
|
||||
r = manager_sign_user_record(m, hr, &signed_hr, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
hr = signed_hr;
|
||||
_fallthrough_;
|
||||
|
||||
case USER_RECORD_SIGNED_EXCLUSIVE:
|
||||
signed_locally = true;
|
||||
break;
|
||||
|
||||
case USER_RECORD_SIGNED:
|
||||
case USER_RECORD_FOREIGN:
|
||||
signed_locally = false;
|
||||
break;
|
||||
|
||||
case -ENOKEY:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_BAD_SIGNATURE, "Specified user record for %s is signed by a key we don't recognize, refusing.", hr->user_name);
|
||||
|
||||
default:
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to validate signature for '%s': %m", hr->user_name);
|
||||
}
|
||||
|
||||
if (uid_is_valid(hr->uid)) {
|
||||
other = hashmap_get(m->homes_by_uid, UID_TO_PTR(hr->uid));
|
||||
if (other)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by home %s, refusing.", hr->uid, other->user_name);
|
||||
|
||||
pw = getpwuid(hr->uid);
|
||||
if (pw)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by NSS user %s, refusing.", hr->uid, pw->pw_name);
|
||||
|
||||
gr = getgrgid(hr->uid);
|
||||
if (gr)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use as GID by NSS group %s, refusing.", hr->uid, gr->gr_name);
|
||||
} else {
|
||||
r = manager_augment_record_with_uid(m, hr);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to acquire UID for '%s': %m", hr->user_name);
|
||||
}
|
||||
|
||||
r = home_new(m, hr, NULL, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(*ret)->signed_locally = signed_locally;
|
||||
return r;
|
||||
}
|
||||
|
||||
static int method_register_home(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
Manager *m = userdata;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(m);
|
||||
|
||||
r = bus_message_read_home_record(message, USER_RECORD_LOAD_EMBEDDED, &hr, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
CAP_SYS_ADMIN,
|
||||
"org.freedesktop.home1.create-home",
|
||||
NULL,
|
||||
true,
|
||||
UID_INVALID,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = validate_and_allocate_home(m, hr, &h, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_save_record(h);
|
||||
if (r < 0) {
|
||||
home_free(h);
|
||||
return r;
|
||||
}
|
||||
|
||||
return sd_bus_reply_method_return(message, NULL);
|
||||
}
|
||||
|
||||
static int method_unregister_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
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) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
Manager *m = userdata;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(m);
|
||||
|
||||
r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE, &hr, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bus_verify_polkit_async(
|
||||
message,
|
||||
CAP_SYS_ADMIN,
|
||||
"org.freedesktop.home1.create-home",
|
||||
NULL,
|
||||
true,
|
||||
UID_INVALID,
|
||||
&m->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* Will call us back */
|
||||
|
||||
r = validate_and_allocate_home(m, hr, &h, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_create(h, hr, error);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
assert(r == 0);
|
||||
h->unregister_on_failure = true;
|
||||
assert(!h->current_operation);
|
||||
|
||||
r = home_set_current_message(h, message);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
(void) home_unlink_record(h);
|
||||
h = home_free(h);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int method_realize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_realize, error);
|
||||
}
|
||||
|
||||
static int method_remove_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_remove, error);
|
||||
}
|
||||
|
||||
static int method_fixate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_fixate, error);
|
||||
}
|
||||
|
||||
static int method_authenticate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_authenticate, error);
|
||||
}
|
||||
|
||||
static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
Manager *m = userdata;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
assert(m);
|
||||
|
||||
r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE, &hr, error);
|
||||
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);
|
||||
}
|
||||
|
||||
static int method_resize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_resize, error);
|
||||
}
|
||||
|
||||
static int method_change_password_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_change_password, error);
|
||||
}
|
||||
|
||||
static int method_lock_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_lock, error);
|
||||
}
|
||||
|
||||
static int method_unlock_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_unlock, error);
|
||||
}
|
||||
|
||||
static int method_acquire_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_acquire, error);
|
||||
}
|
||||
|
||||
static int method_ref_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_ref, error);
|
||||
}
|
||||
|
||||
static int method_release_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
return generic_home_method(userdata, message, bus_home_method_release, error);
|
||||
}
|
||||
|
||||
static int method_lock_all_homes(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
_cleanup_(operation_unrefp) Operation *o = NULL;
|
||||
bool waiting = false;
|
||||
Manager *m = userdata;
|
||||
Iterator i;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
/* This is called from logind when we are preparing for system suspend. We enqueue a lock operation
|
||||
* for every suitable home we have and only when all of them completed we send a reply indicating
|
||||
* completion. */
|
||||
|
||||
HASHMAP_FOREACH(h, m->homes_by_name, i) {
|
||||
|
||||
/* Automatically suspend all homes that have at least one client referencing it that asked
|
||||
* for "please suspend", and no client that asked for "please do not suspend". */
|
||||
if (h->ref_event_source_dont_suspend ||
|
||||
!h->ref_event_source_please_suspend)
|
||||
continue;
|
||||
|
||||
if (!o) {
|
||||
o = operation_new(OPERATION_LOCK_ALL, message);
|
||||
if (!o)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
log_info("Automatically locking of home of user %s.", h->user_name);
|
||||
|
||||
r = home_schedule_operation(h, o, error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
waiting = true;
|
||||
}
|
||||
|
||||
if (waiting) /* At least one lock operation was enqeued, let's leave here without a reply: it will
|
||||
* be sent as soon as the last of the lock operations completed. */
|
||||
return 1;
|
||||
|
||||
return sd_bus_reply_method_return(message, NULL);
|
||||
}
|
||||
|
||||
const sd_bus_vtable manager_vtable[] = {
|
||||
SD_BUS_VTABLE_START(0),
|
||||
|
||||
SD_BUS_PROPERTY("AutoLogin", "a(sso)", property_get_auto_login, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
|
||||
SD_BUS_METHOD("GetHomeByName", "s", "usussso", method_get_home_by_name, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("GetHomeByUID", "u", "ssussso", method_get_home_by_uid, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("GetUserRecordByName", "s", "sbo", method_get_user_record_by_name, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("GetUserRecordByUID", "u", "sbo", method_get_user_record_by_uid, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("ListHomes", NULL, "a(susussso)", method_list_homes, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
|
||||
/* The following methods directly execute an operation on a home, without ref-counting, queing or
|
||||
* anything, and are accessible through homectl. */
|
||||
SD_BUS_METHOD("ActivateHome", "ss", NULL, method_activate_home, SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("DeactivateHome", "s", NULL, method_deactivate_home, 0),
|
||||
SD_BUS_METHOD("RegisterHome", "s", NULL, method_register_home, SD_BUS_VTABLE_UNPRIVILEGED), /* Add JSON record to homed, but don't create actual $HOME */
|
||||
SD_BUS_METHOD("UnregisterHome", "s", NULL, method_unregister_home, SD_BUS_VTABLE_UNPRIVILEGED), /* Remove JSON record from homed, but don't remove actual $HOME */
|
||||
SD_BUS_METHOD("CreateHome", "s", NULL, method_create_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), /* Add JSON record, and create $HOME for it */
|
||||
SD_BUS_METHOD("RealizeHome", "ss", NULL, method_realize_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), /* Create $HOME for already registered JSON entry */
|
||||
SD_BUS_METHOD("RemoveHome", "s", NULL, method_remove_home, SD_BUS_VTABLE_UNPRIVILEGED), /* Remove JSON record and remove $HOME */
|
||||
SD_BUS_METHOD("FixateHome", "ss", NULL, method_fixate_home, SD_BUS_VTABLE_SENSITIVE), /* Investigate $HOME and propagate contained JSON record into our database */
|
||||
SD_BUS_METHOD("AuthenticateHome", "ss", NULL, method_authenticate_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), /* Just check credentials */
|
||||
SD_BUS_METHOD("UpdateHome", "s", NULL, method_update_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), /* Update JSON record of existing user */
|
||||
SD_BUS_METHOD("ResizeHome", "sts", NULL, method_resize_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("ChangePasswordHome", "sss", NULL, method_change_password_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("LockHome", "s", NULL, method_lock_home, 0), /* Prepare active home for system suspend: flush out passwords, suspend access */
|
||||
SD_BUS_METHOD("UnlockHome", "ss", NULL, method_unlock_home, SD_BUS_VTABLE_SENSITIVE), /* Make $HOME usable after system resume again */
|
||||
|
||||
/* The following methods implement ref-counted activation, and are what the PAM module calls (and
|
||||
* what "homectl with" runs). In contrast to the methods above which fail if an operation is already
|
||||
* being executed on a home directory, these ones will queue the request, and are thus more
|
||||
* reliable. Moreover, they are a bit smarter: AcquireHome() will fixate, activate, unlock, or
|
||||
* authenticate depending on the state of the home, so that the end result is always the same
|
||||
* (i.e. the home directory is accessible), and we always validate the specified passwords. RefHome()
|
||||
* will not authenticate, and thus only works if home is already active. */
|
||||
SD_BUS_METHOD("AcquireHome", "ssb", "h", method_acquire_home, SD_BUS_VTABLE_SENSITIVE),
|
||||
SD_BUS_METHOD("RefHome", "sb", "h", method_ref_home, 0),
|
||||
SD_BUS_METHOD("ReleaseHome", "s", NULL, method_release_home, 0),
|
||||
|
||||
/* An operation that acts on all homes that allow it */
|
||||
SD_BUS_METHOD("LockAllHomes", NULL, NULL, method_lock_all_homes, 0),
|
||||
|
||||
SD_BUS_VTABLE_END
|
||||
};
|
||||
|
||||
static int on_deferred_auto_login(sd_event_source *s, void *userdata) {
|
||||
Manager *m = userdata;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
m->deferred_auto_login_event_source = sd_event_source_unref(m->deferred_auto_login_event_source);
|
||||
|
||||
r = sd_bus_emit_properties_changed(
|
||||
m->bus,
|
||||
"/org/freedesktop/home1",
|
||||
"org.freedesktop.home1.Manager",
|
||||
"AutoLogin", NULL);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to send AutoLogin property change event, ignoring: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bus_manager_emit_auto_login_changed(Manager *m) {
|
||||
int r;
|
||||
assert(m);
|
||||
|
||||
if (m->deferred_auto_login_event_source)
|
||||
return 0;
|
||||
|
||||
if (!m->event)
|
||||
return 0;
|
||||
|
||||
if (IN_SET(sd_event_get_state(m->event), SD_EVENT_FINISHED, SD_EVENT_EXITING))
|
||||
return 0;
|
||||
|
||||
r = sd_event_add_defer(m->event, &m->deferred_auto_login_event_source, on_deferred_auto_login, m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate auto login event source: %m");
|
||||
|
||||
r = sd_event_source_set_priority(m->deferred_auto_login_event_source, SD_EVENT_PRIORITY_IDLE+10);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to tweak priority of event source, ignoring: %m");
|
||||
|
||||
(void) sd_event_source_set_description(m->deferred_auto_login_event_source, "deferred-auto-login");
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
extern const sd_bus_vtable manager_vtable[];
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,67 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
#include "sd-device.h"
|
||||
#include "sd-event.h"
|
||||
|
||||
typedef struct Manager Manager;
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "homed-home.h"
|
||||
#include "varlink.h"
|
||||
|
||||
#define HOME_UID_MIN 60001
|
||||
#define HOME_UID_MAX 60513
|
||||
|
||||
struct Manager {
|
||||
sd_event *event;
|
||||
sd_bus *bus;
|
||||
|
||||
Hashmap *polkit_registry;
|
||||
|
||||
Hashmap *homes_by_uid;
|
||||
Hashmap *homes_by_name;
|
||||
Hashmap *homes_by_worker_pid;
|
||||
Hashmap *homes_by_sysfs;
|
||||
|
||||
bool scan_slash_home;
|
||||
|
||||
sd_event_source *inotify_event_source;
|
||||
|
||||
/* An even source we receieve sd_notify() messages from our worker from */
|
||||
sd_event_source *notify_socket_event_source;
|
||||
|
||||
sd_device_monitor *device_monitor;
|
||||
|
||||
sd_event_source *deferred_rescan_event_source;
|
||||
sd_event_source *deferred_gc_event_source;
|
||||
sd_event_source *deferred_auto_login_event_source;
|
||||
|
||||
Home *gc_focus;
|
||||
|
||||
VarlinkServer *varlink_server;
|
||||
|
||||
EVP_PKEY *private_key; /* actually a pair of private and public key */
|
||||
Hashmap *public_keys; /* key name [char*] → publick key [EVP_PKEY*] */
|
||||
};
|
||||
|
||||
int manager_new(Manager **ret);
|
||||
Manager* manager_free(Manager *m);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
|
||||
|
||||
int manager_startup(Manager *m);
|
||||
|
||||
int manager_augment_record_with_uid(Manager *m, UserRecord *hr);
|
||||
|
||||
int manager_enqueue_rescan(Manager *m);
|
||||
int manager_enqueue_gc(Manager *m, Home *focus);
|
||||
|
||||
int manager_verify_user_record(Manager *m, UserRecord *hr);
|
||||
|
||||
int manager_acquire_key_pair(Manager *m);
|
||||
int manager_sign_user_record(Manager *m, UserRecord *u, UserRecord **ret, sd_bus_error *error);
|
||||
|
||||
int bus_manager_emit_auto_login_changed(Manager *m);
|
|
@ -0,0 +1,76 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "fd-util.h"
|
||||
#include "homed-operation.h"
|
||||
|
||||
Operation *operation_new(OperationType type, sd_bus_message *m) {
|
||||
Operation *o;
|
||||
|
||||
assert(type >= 0);
|
||||
assert(type < _OPERATION_MAX);
|
||||
|
||||
o = new(Operation, 1);
|
||||
if (!o)
|
||||
return NULL;
|
||||
|
||||
*o = (Operation) {
|
||||
.type = type,
|
||||
.n_ref = 1,
|
||||
.message = sd_bus_message_ref(m),
|
||||
.send_fd = -1,
|
||||
.result = -1,
|
||||
};
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
static Operation *operation_free(Operation *o) {
|
||||
int r;
|
||||
|
||||
if (!o)
|
||||
return NULL;
|
||||
|
||||
if (o->message && o->result >= 0) {
|
||||
|
||||
if (o->result) {
|
||||
/* Propagate success */
|
||||
if (o->send_fd < 0)
|
||||
r = sd_bus_reply_method_return(o->message, NULL);
|
||||
else
|
||||
r = sd_bus_reply_method_return(o->message, "h", o->send_fd);
|
||||
|
||||
} else {
|
||||
/* Propagate failure */
|
||||
if (sd_bus_error_is_set(&o->error))
|
||||
r = sd_bus_reply_method_error(o->message, &o->error);
|
||||
else
|
||||
r = sd_bus_reply_method_errnof(o->message, o->ret, "Failed to execute operation: %m");
|
||||
}
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed ot reply to %s method call, ignoring: %m", sd_bus_message_get_member(o->message));
|
||||
}
|
||||
|
||||
sd_bus_message_unref(o->message);
|
||||
user_record_unref(o->secret);
|
||||
safe_close(o->send_fd);
|
||||
sd_bus_error_free(&o->error);
|
||||
|
||||
return mfree(o);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_REF_UNREF_FUNC(Operation, operation, operation_free);
|
||||
|
||||
void operation_result(Operation *o, int ret, const sd_bus_error *error) {
|
||||
assert(o);
|
||||
|
||||
if (ret >= 0)
|
||||
o->result = true;
|
||||
else {
|
||||
o->ret = ret;
|
||||
|
||||
sd_bus_error_free(&o->error);
|
||||
sd_bus_error_copy(&o->error, error);
|
||||
|
||||
o->result = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <sd-bus.h>
|
||||
|
||||
#include "user-record.h"
|
||||
|
||||
typedef enum OperationType {
|
||||
OPERATION_ACQUIRE, /* enqueued on AcquireHome() */
|
||||
OPERATION_RELEASE, /* enqueued on ReleaseHome() */
|
||||
OPERATION_LOCK_ALL, /* enqueued on LockAllHomes() */
|
||||
OPERATION_PIPE_EOF, /* enqueued when we see EOF on the per-home reference pipes */
|
||||
OPERATION_DEACTIVATE_FORCE, /* enqueued on hard $HOME unplug */
|
||||
OPERATION_IMMEDIATE, /* this is never enqueued, it's just a marker we immediately started executing an operation without enqueuing anything first. */
|
||||
_OPERATION_MAX,
|
||||
_OPERATION_INVALID = -1,
|
||||
} OperationType;
|
||||
|
||||
/* Encapsulates an operation on one or more home directories. This has two uses:
|
||||
*
|
||||
* 1) For queuing an operation when we need to execute one for some reason but there's already one being
|
||||
* executed.
|
||||
*
|
||||
* 2) When executing an operation without enqueuing it first (OPERATION_IMMEDIATE)
|
||||
*
|
||||
* Note that a single operation object can encapsulate operations on multiple home directories. This is used
|
||||
* for the LockAllHomes() operation, which is one operation but applies to all homes at once. In case the
|
||||
* operation applies to multiple homes the reference counter is increased once for each, and thus the
|
||||
* operation is fully completed only after it reached zero again.
|
||||
*
|
||||
* The object (optionally) contains a reference of the D-Bus message triggering the operation, which is
|
||||
* replied to when the operation is fully completed, i.e. when n_ref reaches zero.
|
||||
*/
|
||||
|
||||
typedef struct Operation {
|
||||
unsigned n_ref;
|
||||
OperationType type;
|
||||
sd_bus_message *message;
|
||||
|
||||
UserRecord *secret;
|
||||
int send_fd; /* pipe fd for AcquireHome() which is taken already when we start the operation */
|
||||
|
||||
int result; /* < 0 if not completed yet, == 0 on failure, > 0 on success */
|
||||
sd_bus_error error;
|
||||
int ret;
|
||||
} Operation;
|
||||
|
||||
Operation *operation_new(OperationType type, sd_bus_message *m);
|
||||
Operation *operation_ref(Operation *operation);
|
||||
Operation *operation_unref(Operation *operation);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Operation*, operation_unref);
|
||||
|
||||
void operation_result(Operation *o, int ret, const sd_bus_error *error);
|
||||
|
||||
static inline Operation* operation_result_unref(Operation *o, int ret, const sd_bus_error *error) {
|
||||
if (!o)
|
||||
return NULL;
|
||||
|
||||
operation_result(o, ret, error);
|
||||
return operation_unref(o);
|
||||
}
|
|
@ -0,0 +1,370 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "group-record.h"
|
||||
#include "homed-varlink.h"
|
||||
#include "strv.h"
|
||||
#include "user-record-util.h"
|
||||
#include "user-record.h"
|
||||
#include "user-util.h"
|
||||
#include "format-util.h"
|
||||
|
||||
typedef struct LookupParameters {
|
||||
const char *user_name;
|
||||
const char *group_name;
|
||||
union {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
};
|
||||
const char *service;
|
||||
} LookupParameters;
|
||||
|
||||
static bool client_is_trusted(Varlink *link, Home *h) {
|
||||
uid_t peer_uid;
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(h);
|
||||
|
||||
r = varlink_get_peer_uid(link, &peer_uid);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Unable to query peer UID, ignoring: %m");
|
||||
return false;
|
||||
}
|
||||
|
||||
return peer_uid == 0 || peer_uid == h->uid;
|
||||
}
|
||||
|
||||
static int build_user_json(Home *h, bool trusted, JsonVariant **ret) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *augmented = NULL;
|
||||
UserRecordLoadFlags flags;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(ret);
|
||||
|
||||
flags = USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_BINDING|USER_RECORD_STRIP_SECRET|USER_RECORD_ALLOW_STATUS|USER_RECORD_ALLOW_SIGNATURE;
|
||||
if (trusted)
|
||||
flags |= USER_RECORD_ALLOW_PRIVILEGED;
|
||||
else
|
||||
flags |= USER_RECORD_STRIP_PRIVILEGED;
|
||||
|
||||
r = home_augment_status(h, flags, &augmented);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return json_build(ret, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(augmented->json)),
|
||||
JSON_BUILD_PAIR("incomplete", JSON_BUILD_BOOLEAN(augmented->incomplete))));
|
||||
}
|
||||
|
||||
static bool home_user_match_lookup_parameters(LookupParameters *p, Home *h) {
|
||||
assert(p);
|
||||
assert(h);
|
||||
|
||||
if (p->user_name && !streq(p->user_name, h->user_name))
|
||||
return false;
|
||||
|
||||
if (uid_is_valid(p->uid) && h->uid != p->uid)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 },
|
||||
{ "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), JSON_SAFE },
|
||||
{ "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
LookupParameters p = {
|
||||
.uid = UID_INVALID,
|
||||
};
|
||||
Manager *m = userdata;
|
||||
bool trusted;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(parameters);
|
||||
assert(m);
|
||||
|
||||
r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!streq_ptr(p.service, "io.systemd.Home"))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
|
||||
|
||||
if (uid_is_valid(p.uid))
|
||||
h = hashmap_get(m->homes_by_uid, UID_TO_PTR(p.uid));
|
||||
else if (p.user_name)
|
||||
h = hashmap_get(m->homes_by_name, p.user_name);
|
||||
else {
|
||||
Iterator i;
|
||||
|
||||
/* If neither UID nor name was specified, then dump all homes. Do so with varlink_notify()
|
||||
* for all entries but the last, so that clients can stream the results, and easily process
|
||||
* them piecemeal. */
|
||||
|
||||
HASHMAP_FOREACH(h, m->homes_by_name, i) {
|
||||
|
||||
if (!home_user_match_lookup_parameters(&p, h))
|
||||
continue;
|
||||
|
||||
if (v) {
|
||||
/* An entry set from the previous iteration? Then send it now */
|
||||
r = varlink_notify(link, v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
v = json_variant_unref(v);
|
||||
}
|
||||
|
||||
trusted = client_is_trusted(link, h);
|
||||
|
||||
r = build_user_json(h, trusted, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!v)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
return varlink_reply(link, v);
|
||||
}
|
||||
|
||||
if (!h)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
if (!home_user_match_lookup_parameters(&p, h))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
|
||||
|
||||
trusted = client_is_trusted(link, h);
|
||||
|
||||
r = build_user_json(h, trusted, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return varlink_reply(link, v);
|
||||
}
|
||||
|
||||
static int build_group_json(Home *h, JsonVariant **ret) {
|
||||
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(ret);
|
||||
|
||||
g = group_record_new();
|
||||
if (!g)
|
||||
return -ENOMEM;
|
||||
|
||||
r = group_record_synthesize(g, h->record);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(!FLAGS_SET(g->mask, USER_RECORD_SECRET));
|
||||
assert(!FLAGS_SET(g->mask, USER_RECORD_PRIVILEGED));
|
||||
|
||||
return json_build(ret,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(g->json))));
|
||||
}
|
||||
|
||||
static bool home_group_match_lookup_parameters(LookupParameters *p, Home *h) {
|
||||
assert(p);
|
||||
assert(h);
|
||||
|
||||
if (p->group_name && !streq(h->user_name, p->group_name))
|
||||
return false;
|
||||
|
||||
if (gid_is_valid(p->gid) && h->uid != (uid_t) p->gid)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 },
|
||||
{ "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), JSON_SAFE },
|
||||
{ "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
LookupParameters p = {
|
||||
.gid = GID_INVALID,
|
||||
};
|
||||
Manager *m = userdata;
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(parameters);
|
||||
assert(m);
|
||||
|
||||
r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!streq_ptr(p.service, "io.systemd.Home"))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
|
||||
|
||||
if (gid_is_valid(p.gid))
|
||||
h = hashmap_get(m->homes_by_uid, UID_TO_PTR((uid_t) p.gid));
|
||||
else if (p.group_name)
|
||||
h = hashmap_get(m->homes_by_name, p.group_name);
|
||||
else {
|
||||
Iterator i;
|
||||
|
||||
HASHMAP_FOREACH(h, m->homes_by_name, i) {
|
||||
|
||||
if (!home_group_match_lookup_parameters(&p, h))
|
||||
continue;
|
||||
|
||||
if (v) {
|
||||
r = varlink_notify(link, v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
v = json_variant_unref(v);
|
||||
}
|
||||
|
||||
r = build_group_json(h, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!v)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
return varlink_reply(link, v);
|
||||
}
|
||||
|
||||
if (!h)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
if (!home_group_match_lookup_parameters(&p, h))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
|
||||
|
||||
r = build_group_json(h, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return varlink_reply(link, v);
|
||||
}
|
||||
|
||||
int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), JSON_SAFE },
|
||||
{ "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), JSON_SAFE },
|
||||
{ "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
Manager *m = userdata;
|
||||
LookupParameters p = {};
|
||||
Home *h;
|
||||
int r;
|
||||
|
||||
assert(parameters);
|
||||
assert(m);
|
||||
|
||||
r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!streq_ptr(p.service, "io.systemd.Home"))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
|
||||
|
||||
if (p.user_name) {
|
||||
const char *last = NULL;
|
||||
char **i;
|
||||
|
||||
h = hashmap_get(m->homes_by_name, p.user_name);
|
||||
if (!h)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
if (p.group_name) {
|
||||
if (!strv_contains(h->record->member_of, p.group_name))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name))));
|
||||
}
|
||||
|
||||
STRV_FOREACH(i, h->record->member_of) {
|
||||
if (last) {
|
||||
r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last))));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
last = *i;
|
||||
}
|
||||
|
||||
if (last)
|
||||
return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last))));
|
||||
|
||||
} else if (p.group_name) {
|
||||
const char *last = NULL;
|
||||
Iterator i;
|
||||
|
||||
HASHMAP_FOREACH(h, m->homes_by_name, i) {
|
||||
|
||||
if (!strv_contains(h->record->member_of, p.group_name))
|
||||
continue;
|
||||
|
||||
if (last) {
|
||||
r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name))));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
last = h->user_name;
|
||||
}
|
||||
|
||||
if (last)
|
||||
return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name))));
|
||||
} else {
|
||||
const char *last_user_name = NULL, *last_group_name = NULL;
|
||||
Iterator i;
|
||||
|
||||
HASHMAP_FOREACH(h, m->homes_by_name, i) {
|
||||
char **j;
|
||||
|
||||
STRV_FOREACH(j, h->record->member_of) {
|
||||
|
||||
if (last_user_name) {
|
||||
assert(last_group_name);
|
||||
|
||||
r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name))));
|
||||
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
last_user_name = h->user_name;
|
||||
last_group_name = *j;
|
||||
}
|
||||
}
|
||||
|
||||
if (last_user_name) {
|
||||
assert(last_group_name);
|
||||
return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name))));
|
||||
}
|
||||
}
|
||||
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "homed-manager.h"
|
||||
|
||||
int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
|
||||
int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
|
||||
int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
|
|
@ -0,0 +1,46 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "daemon-util.h"
|
||||
#include "homed-manager.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "signal-util.h"
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(notify_on_cleanup) const char *notify_stop = NULL;
|
||||
_cleanup_(manager_freep) Manager *m = NULL;
|
||||
int r;
|
||||
|
||||
log_setup_service();
|
||||
|
||||
umask(0022);
|
||||
|
||||
if (argc != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
|
||||
|
||||
if (setenv("SYSTEMD_BYPASS_USERDB", "io.systemd.Home", 1) < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set $SYSTEMD_BYPASS_USERDB: %m");
|
||||
|
||||
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, -1) >= 0);
|
||||
|
||||
r = manager_new(&m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not create manager: %m");
|
||||
|
||||
r = manager_startup(m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to start up daemon: %m");
|
||||
|
||||
notify_stop = notify_start(NOTIFY_READY, NOTIFY_STOPPING);
|
||||
|
||||
r = sd_event_loop(m->event);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Event loop failed: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
|
@ -0,0 +1,215 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "dirent-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "homework-cifs.h"
|
||||
#include "homework-mount.h"
|
||||
#include "mount-util.h"
|
||||
#include "process-util.h"
|
||||
#include "strv.h"
|
||||
#include "tmpfile-util.h"
|
||||
|
||||
int home_prepare_cifs(
|
||||
UserRecord *h,
|
||||
bool already_activated,
|
||||
HomeSetup *setup) {
|
||||
|
||||
char **pw;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(setup);
|
||||
assert(user_record_storage(h) == USER_CIFS);
|
||||
|
||||
if (already_activated)
|
||||
setup->root_fd = open(user_record_home_directory(h), O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
||||
else {
|
||||
bool mounted = false;
|
||||
|
||||
r = home_unshare_and_mount(NULL, NULL, false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH(pw, h->password) {
|
||||
_cleanup_(unlink_and_freep) char *p = NULL;
|
||||
_cleanup_free_ char *options = NULL;
|
||||
_cleanup_(fclosep) FILE *f = NULL;
|
||||
pid_t mount_pid;
|
||||
int exit_status;
|
||||
|
||||
r = fopen_temporary(NULL, &f, &p);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create temporary credentials file: %m");
|
||||
|
||||
fprintf(f,
|
||||
"username=%s\n"
|
||||
"password=%s\n",
|
||||
user_record_cifs_user_name(h),
|
||||
*pw);
|
||||
|
||||
if (h->cifs_domain)
|
||||
fprintf(f, "domain=%s\n", h->cifs_domain);
|
||||
|
||||
r = fflush_and_check(f);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write temporary credentials file: %m");
|
||||
|
||||
f = safe_fclose(f);
|
||||
|
||||
if (asprintf(&options, "credentials=%s,uid=" UID_FMT ",forceuid,gid=" UID_FMT ",forcegid,file_mode=0%3o,dir_mode=0%3o",
|
||||
p, h->uid, h->uid, h->access_mode, h->access_mode) < 0)
|
||||
return log_oom();
|
||||
|
||||
r = safe_fork("(mount)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_STDOUT_TO_STDERR, &mount_pid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) {
|
||||
/* Child */
|
||||
execl("/bin/mount", "/bin/mount", "-n", "-t", "cifs",
|
||||
h->cifs_service, "/run/systemd/user-home-mount",
|
||||
"-o", options, NULL);
|
||||
|
||||
log_error_errno(errno, "Failed to execute fsck: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
exit_status = wait_for_terminate_and_check("mount", mount_pid, WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS);
|
||||
if (exit_status < 0)
|
||||
return exit_status;
|
||||
if (exit_status != EXIT_SUCCESS)
|
||||
return -EPROTO;
|
||||
|
||||
mounted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!mounted)
|
||||
return log_error_errno(ENOKEY, "Failed to mount home directory with supplied password.");
|
||||
|
||||
setup->root_fd = open("/run/systemd/user-home-mount", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
||||
}
|
||||
if (setup->root_fd < 0)
|
||||
return log_error_errno(r, "Failed to open home directory: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int home_activate_cifs(
|
||||
UserRecord *h,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
UserRecord **ret_home) {
|
||||
|
||||
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
|
||||
const char *hdo, *hd;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(user_record_storage(h) == USER_CIFS);
|
||||
assert(ret_home);
|
||||
|
||||
if (!h->cifs_service)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks CIFS service, refusing.");
|
||||
|
||||
assert_se(hdo = user_record_home_directory(h));
|
||||
hd = strdupa(hdo); /* copy the string out, since it might change later in the home record object */
|
||||
|
||||
r = home_prepare_cifs(h, false, &setup);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_refresh(h, &setup, NULL, pkcs11_decrypted_passwords, NULL, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
setup.root_fd = safe_close(setup.root_fd);
|
||||
|
||||
r = home_move_mount(NULL, hd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
setup.undo_mount = false;
|
||||
|
||||
log_info("Everything completed.");
|
||||
|
||||
*ret_home = TAKE_PTR(new_home);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int home_create_cifs(UserRecord *h, UserRecord **ret_home) {
|
||||
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
|
||||
_cleanup_(closedirp) DIR *d = NULL;
|
||||
int r, copy;
|
||||
|
||||
assert(h);
|
||||
assert(user_record_storage(h) == USER_CIFS);
|
||||
assert(ret_home);
|
||||
|
||||
if (!h->cifs_service)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks CIFS service, refusing.");
|
||||
|
||||
if (access("/sbin/mount.cifs", F_OK) < 0) {
|
||||
if (errno == ENOENT)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), "/sbin/mount.cifs is missing.");
|
||||
|
||||
return log_error_errno(errno, "Unable to detect whether /sbin/mount.cifs exists: %m");
|
||||
}
|
||||
|
||||
r = home_prepare_cifs(h, false, &setup);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
copy = fcntl(setup.root_fd, F_DUPFD_CLOEXEC, 3);
|
||||
if (copy < 0)
|
||||
return -errno;
|
||||
|
||||
d = fdopendir(copy);
|
||||
if (!d) {
|
||||
safe_close(copy);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
if (readdir_no_dot(d))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTEMPTY), "Selected CIFS directory not empty, refusing.");
|
||||
if (errno != 0)
|
||||
return log_error_errno(errno, "Failed to detect if CIFS directory is empty: %m");
|
||||
|
||||
r = home_populate(h, setup.root_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_sync_and_statfs(setup.root_fd, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET, &new_home);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to clone record: %m");
|
||||
|
||||
r = user_record_add_binding(
|
||||
new_home,
|
||||
USER_CIFS,
|
||||
NULL,
|
||||
SD_ID128_NULL,
|
||||
SD_ID128_NULL,
|
||||
SD_ID128_NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
UINT64_MAX,
|
||||
NULL,
|
||||
NULL,
|
||||
h->uid,
|
||||
(gid_t) h->uid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add binding to record: %m");
|
||||
|
||||
log_info("Everything completed.");
|
||||
|
||||
*ret_home = TAKE_PTR(new_home);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "homework.h"
|
||||
#include "user-record.h"
|
||||
|
||||
int home_prepare_cifs(UserRecord *h, bool already_activated, HomeSetup *setup);
|
||||
|
||||
int home_activate_cifs(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
|
||||
|
||||
int home_create_cifs(UserRecord *h, UserRecord **ret_home);
|
|
@ -0,0 +1,242 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include "btrfs-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "homework-directory.h"
|
||||
#include "homework-quota.h"
|
||||
#include "mkdir.h"
|
||||
#include "mount-util.h"
|
||||
#include "path-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "umask-util.h"
|
||||
|
||||
int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *setup) {
|
||||
assert(h);
|
||||
assert(setup);
|
||||
|
||||
setup->root_fd = open(user_record_image_path(h), O_RDONLY|O_CLOEXEC|O_DIRECTORY);
|
||||
if (setup->root_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open home directory: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int home_activate_directory(
|
||||
UserRecord *h,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
UserRecord **ret_home) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL;
|
||||
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
|
||||
const char *hdo, *hd, *ipo, *ip;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT));
|
||||
assert(ret_home);
|
||||
|
||||
assert_se(ipo = user_record_image_path(h));
|
||||
ip = strdupa(ipo); /* copy out, since reconciliation might cause changing of the field */
|
||||
|
||||
assert_se(hdo = user_record_home_directory(h));
|
||||
hd = strdupa(hdo);
|
||||
|
||||
r = home_prepare(h, false, pkcs11_decrypted_passwords, &setup, &header_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_refresh(h, &setup, header_home, pkcs11_decrypted_passwords, NULL, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
setup.root_fd = safe_close(setup.root_fd);
|
||||
|
||||
/* Create mount point to mount over if necessary */
|
||||
if (!path_equal(ip, hd))
|
||||
(void) mkdir_p(hd, 0700);
|
||||
|
||||
/* Create a mount point (even if the directory is already placed correctly), as a way to indicate
|
||||
* this mount point is now "activated". Moreover, we want to set per-user
|
||||
* MS_NOSUID/MS_NOEXEC/MS_NODEV. */
|
||||
r = mount_verbose(LOG_ERR, ip, hd, NULL, MS_BIND, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = mount_verbose(LOG_ERR, NULL, hd, NULL, MS_BIND|MS_REMOUNT|user_record_mount_flags(h), NULL);
|
||||
if (r < 0) {
|
||||
(void) umount_verbose(hd);
|
||||
return r;
|
||||
}
|
||||
|
||||
log_info("Everything completed.");
|
||||
|
||||
*ret_home = TAKE_PTR(new_home);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home) {
|
||||
_cleanup_(rm_rf_subvolume_and_freep) char *temporary = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
|
||||
_cleanup_close_ int root_fd = -1;
|
||||
_cleanup_free_ char *d = NULL;
|
||||
const char *ip;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME));
|
||||
assert(ret_home);
|
||||
|
||||
assert_se(ip = user_record_image_path(h));
|
||||
|
||||
r = tempfn_random(ip, "homework", &d);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate temporary directory: %m");
|
||||
|
||||
(void) mkdir_parents(d, 0755);
|
||||
|
||||
switch (user_record_storage(h)) {
|
||||
|
||||
case USER_SUBVOLUME:
|
||||
RUN_WITH_UMASK(0077)
|
||||
r = btrfs_subvol_make(d);
|
||||
|
||||
if (r >= 0) {
|
||||
log_info("Subvolume created.");
|
||||
|
||||
if (h->disk_size != UINT64_MAX) {
|
||||
|
||||
/* Enable quota for the subvolume we just created. Note we don't check for
|
||||
* errors here and only log about debug level about this. */
|
||||
r = btrfs_quota_enable(d, true);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to enable quota on %s, ignoring: %m", d);
|
||||
|
||||
r = btrfs_subvol_auto_qgroup(d, 0, false);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to set up automatic quota group on %s, ignoring: %m", d);
|
||||
|
||||
/* Actually configure the quota. We also ignore errors here, but we do log
|
||||
* about them loudly, to keep things discoverable even though we don't
|
||||
* consider lacking quota support in kernel fatal. */
|
||||
(void) home_update_quota_btrfs(h, d);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
if (r != -ENOTTY)
|
||||
return log_error_errno(r, "Failed to create temporary home directory subvolume %s: %m", d);
|
||||
|
||||
log_info("Creating subvolume %s is not supported, as file system does not support subvolumes. Falling back to regular directory.", d);
|
||||
_fallthrough_;
|
||||
|
||||
case USER_DIRECTORY:
|
||||
|
||||
if (mkdir(d, 0700) < 0)
|
||||
return log_error_errno(errno, "Failed to create temporary home directory %s: %m", d);
|
||||
|
||||
(void) home_update_quota_classic(h, d);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached("unexpected storage");
|
||||
}
|
||||
|
||||
temporary = TAKE_PTR(d); /* Needs to be destroyed now */
|
||||
|
||||
root_fd = open(temporary, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
||||
if (root_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open temporary home directory: %m");
|
||||
|
||||
r = home_populate(h, root_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_sync_and_statfs(root_fd, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET, &new_home);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to clone record: %m");
|
||||
|
||||
r = user_record_add_binding(
|
||||
new_home,
|
||||
user_record_storage(h),
|
||||
ip,
|
||||
SD_ID128_NULL,
|
||||
SD_ID128_NULL,
|
||||
SD_ID128_NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
UINT64_MAX,
|
||||
NULL,
|
||||
NULL,
|
||||
h->uid,
|
||||
(gid_t) h->uid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add binding to record: %m");
|
||||
|
||||
if (rename(temporary, ip) < 0)
|
||||
return log_error_errno(errno, "Failed to rename %s to %s: %m", temporary, ip);
|
||||
|
||||
temporary = mfree(temporary);
|
||||
|
||||
log_info("Everything completed.");
|
||||
|
||||
*ret_home = TAKE_PTR(new_home);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int home_resize_directory(
|
||||
UserRecord *h,
|
||||
bool already_activated,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
HomeSetup *setup,
|
||||
UserRecord **ret_home) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *new_home = NULL;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(setup);
|
||||
assert(ret_home);
|
||||
assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT));
|
||||
|
||||
r = home_prepare(h, already_activated, pkcs11_decrypted_passwords, setup, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_update_quota_auto(h, NULL);
|
||||
if (ERRNO_IS_NOT_SUPPORTED(r))
|
||||
return -ESOCKTNOSUPPORT; /* make recognizable */
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_extend_embedded_identity(new_home, h, setup);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_sync_and_statfs(setup->root_fd, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_setup_undo(setup);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_info("Everything completed.");
|
||||
|
||||
*ret_home = TAKE_PTR(new_home);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "homework.h"
|
||||
#include "user-record.h"
|
||||
|
||||
int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *setup);
|
||||
int home_activate_directory(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
|
||||
int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home);
|
||||
int home_resize_directory(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
|
|
@ -0,0 +1,644 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "homework-fscrypt.h"
|
||||
#include "homework-quota.h"
|
||||
#include "memory-util.h"
|
||||
#include "missing_keyctl.h"
|
||||
#include "missing_syscall.h"
|
||||
#include "mkdir.h"
|
||||
#include "nulstr-util.h"
|
||||
#include "openssl-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "process-util.h"
|
||||
#include "random-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "stdio-util.h"
|
||||
#include "strv.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "user-util.h"
|
||||
#include "xattr-util.h"
|
||||
|
||||
static int fscrypt_upload_volume_key(
|
||||
const uint8_t key_descriptor[static FS_KEY_DESCRIPTOR_SIZE],
|
||||
const void *volume_key,
|
||||
size_t volume_key_size,
|
||||
key_serial_t where) {
|
||||
|
||||
_cleanup_free_ char *hex = NULL;
|
||||
const char *description;
|
||||
struct fscrypt_key key;
|
||||
key_serial_t serial;
|
||||
|
||||
assert(key_descriptor);
|
||||
assert(volume_key);
|
||||
assert(volume_key_size > 0);
|
||||
|
||||
if (volume_key_size > sizeof(key.raw))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume key too long.");
|
||||
|
||||
hex = hexmem(key_descriptor, FS_KEY_DESCRIPTOR_SIZE);
|
||||
if (!hex)
|
||||
return log_oom();
|
||||
|
||||
description = strjoina("fscrypt:", hex);
|
||||
|
||||
key = (struct fscrypt_key) {
|
||||
.size = volume_key_size,
|
||||
};
|
||||
memcpy(key.raw, volume_key, volume_key_size);
|
||||
|
||||
/* Upload to the kernel */
|
||||
serial = add_key("logon", description, &key, sizeof(key), where);
|
||||
explicit_bzero_safe(&key, sizeof(key));
|
||||
|
||||
if (serial < 0)
|
||||
return log_error_errno(errno, "Failed to install master key in keyring: %m");
|
||||
|
||||
log_info("Uploaded encryption key to kernel.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void calculate_key_descriptor(
|
||||
const void *key,
|
||||
size_t key_size,
|
||||
uint8_t ret_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE]) {
|
||||
|
||||
uint8_t hashed[512 / 8] = {}, hashed2[512 / 8] = {};
|
||||
|
||||
/* Derive the key descriptor from the volume key via double SHA512, in order to be compatible with e4crypt */
|
||||
|
||||
assert_se(SHA512(key, key_size, hashed) == hashed);
|
||||
assert_se(SHA512(hashed, sizeof(hashed), hashed2) == hashed2);
|
||||
|
||||
assert_cc(sizeof(hashed2) >= FS_KEY_DESCRIPTOR_SIZE);
|
||||
|
||||
memcpy(ret_key_descriptor, hashed2, FS_KEY_DESCRIPTOR_SIZE);
|
||||
}
|
||||
|
||||
static int fscrypt_slot_try_one(
|
||||
const char *password,
|
||||
const void *salt, size_t salt_size,
|
||||
const void *encrypted, size_t encrypted_size,
|
||||
const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE],
|
||||
void **ret_decrypted, size_t *ret_decrypted_size) {
|
||||
|
||||
|
||||
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
|
||||
_cleanup_(erase_and_freep) void *decrypted = NULL;
|
||||
uint8_t key_descriptor[FS_KEY_DESCRIPTOR_SIZE];
|
||||
int decrypted_size_out1, decrypted_size_out2;
|
||||
uint8_t derived[512 / 8] = {};
|
||||
size_t decrypted_size;
|
||||
const EVP_CIPHER *cc;
|
||||
int r;
|
||||
|
||||
assert(password);
|
||||
assert(salt);
|
||||
assert(salt_size > 0);
|
||||
assert(encrypted);
|
||||
assert(encrypted_size > 0);
|
||||
assert(match_key_descriptor);
|
||||
|
||||
/* Our construction is like this:
|
||||
*
|
||||
* 1. In each key slot we store a salt value plus the encrypted volume key
|
||||
*
|
||||
* 2. Unlocking is via calculating PBKDF2-HMAC-SHA512 of the supplied password (in combination with
|
||||
* the salt), then using the first 256 bit of the hash as key for decrypting the encrypted
|
||||
* volume key in AES256 counter mode.
|
||||
*
|
||||
* 3. Writing a password is similar: calculate PBKDF2-HMAC-SHA512 of the supplied password (in
|
||||
* combination with the salt), then encrypt the volume key in AES256 counter mode with the
|
||||
* resulting hash.
|
||||
*/
|
||||
|
||||
if (PKCS5_PBKDF2_HMAC(
|
||||
password, strlen(password),
|
||||
salt, salt_size,
|
||||
0xFFFF, EVP_sha512(),
|
||||
sizeof(derived), derived) != 1) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
context = EVP_CIPHER_CTX_new();
|
||||
if (!context) {
|
||||
r = log_oom();
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* We use AES256 in counter mode */
|
||||
assert_se(cc = EVP_aes_256_ctr());
|
||||
|
||||
/* We only use the first half of the derived key */
|
||||
assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc));
|
||||
|
||||
if (EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context.");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Flush out the derived key now, we don't need it anymore */
|
||||
explicit_bzero_safe(derived, sizeof(derived));
|
||||
|
||||
decrypted_size = encrypted_size + EVP_CIPHER_key_length(cc) * 2;
|
||||
decrypted = malloc(decrypted_size);
|
||||
if (!decrypted)
|
||||
return log_oom();
|
||||
|
||||
if (EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted, encrypted_size) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt volume key.");
|
||||
|
||||
assert((size_t) decrypted_size_out1 <= decrypted_size);
|
||||
|
||||
if (EVP_DecryptFinal_ex(context, (uint8_t*) decrypted_size + decrypted_size_out1, &decrypted_size_out2) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish decryption of volume key.");
|
||||
|
||||
assert((size_t) decrypted_size_out1 + (size_t) decrypted_size_out2 < decrypted_size);
|
||||
decrypted_size = (size_t) decrypted_size_out1 + (size_t) decrypted_size_out2;
|
||||
|
||||
calculate_key_descriptor(decrypted, decrypted_size, key_descriptor);
|
||||
|
||||
if (memcmp(key_descriptor, match_key_descriptor, FS_KEY_DESCRIPTOR_SIZE) != 0)
|
||||
return -ENOANO; /* don't log here */
|
||||
|
||||
r = fscrypt_upload_volume_key(key_descriptor, decrypted, decrypted_size, KEY_SPEC_THREAD_KEYRING);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (ret_decrypted)
|
||||
*ret_decrypted = TAKE_PTR(decrypted);
|
||||
if (ret_decrypted_size)
|
||||
*ret_decrypted_size = decrypted_size;
|
||||
|
||||
return 0;
|
||||
|
||||
finish:
|
||||
explicit_bzero_safe(derived, sizeof(derived));
|
||||
return r;
|
||||
}
|
||||
|
||||
static int fscrypt_slot_try_many(
|
||||
char **passwords,
|
||||
const void *salt, size_t salt_size,
|
||||
const void *encrypted, size_t encrypted_size,
|
||||
const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE],
|
||||
void **ret_decrypted, size_t *ret_decrypted_size) {
|
||||
|
||||
char **i;
|
||||
int r;
|
||||
|
||||
STRV_FOREACH(i, passwords) {
|
||||
r = fscrypt_slot_try_one(*i, salt, salt_size, encrypted, encrypted_size, match_key_descriptor, ret_decrypted, ret_decrypted_size);
|
||||
if (r != -ENOANO)
|
||||
return r;
|
||||
}
|
||||
|
||||
return -ENOANO;
|
||||
}
|
||||
|
||||
static int fscrypt_setup(
|
||||
char **pkcs11_decrypted_passwords,
|
||||
char **password,
|
||||
HomeSetup *setup,
|
||||
void **ret_volume_key,
|
||||
size_t *ret_volume_key_size) {
|
||||
|
||||
_cleanup_free_ char *xattr_buf = NULL;
|
||||
const char *xa;
|
||||
int r;
|
||||
|
||||
assert(setup);
|
||||
assert(setup->root_fd >= 0);
|
||||
|
||||
r = flistxattr_malloc(setup->root_fd, &xattr_buf);
|
||||
if (r < 0)
|
||||
return log_error_errno(errno, "Failed to retrieve xattr list: %m");
|
||||
|
||||
NULSTR_FOREACH(xa, xattr_buf) {
|
||||
_cleanup_free_ void *salt = NULL, *encrypted = NULL;
|
||||
_cleanup_free_ char *value = NULL;
|
||||
size_t salt_size, encrypted_size;
|
||||
const char *nr, *e;
|
||||
int n;
|
||||
|
||||
/* Check if this xattr has the format 'trusted.fscrypt_slot<nr>' where '<nr>' is a 32bit unsigned integer */
|
||||
nr = startswith(xa, "trusted.fscrypt_slot");
|
||||
if (!nr)
|
||||
continue;
|
||||
if (safe_atou32(nr, NULL) < 0)
|
||||
continue;
|
||||
|
||||
n = fgetxattr_malloc(setup->root_fd, xa, &value);
|
||||
if (n == -ENODATA) /* deleted by now? */
|
||||
continue;
|
||||
if (n < 0)
|
||||
return log_error_errno(n, "Failed to read %s xattr: %m", xa);
|
||||
|
||||
e = memchr(value, ':', n);
|
||||
if (!e)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "xattr %s lacks ':' separator: %m", xa);
|
||||
|
||||
r = unbase64mem(value, e - value, &salt, &salt_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to decode salt of %s: %m", xa);
|
||||
r = unbase64mem(e+1, n - (e - value) - 1, &encrypted, &encrypted_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa);
|
||||
|
||||
r = fscrypt_slot_try_many(
|
||||
pkcs11_decrypted_passwords,
|
||||
salt, salt_size,
|
||||
encrypted, encrypted_size,
|
||||
setup->fscrypt_key_descriptor,
|
||||
ret_volume_key, ret_volume_key_size);
|
||||
if (r == -ENOANO)
|
||||
r = fscrypt_slot_try_many(
|
||||
password,
|
||||
salt, salt_size,
|
||||
encrypted, encrypted_size,
|
||||
setup->fscrypt_key_descriptor,
|
||||
ret_volume_key, ret_volume_key_size);
|
||||
if (r < 0) {
|
||||
if (r != -ENOANO)
|
||||
return r;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to set up home directory with provided passwords.");
|
||||
}
|
||||
|
||||
int home_prepare_fscrypt(
|
||||
UserRecord *h,
|
||||
bool already_activated,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
HomeSetup *setup) {
|
||||
|
||||
_cleanup_(erase_and_freep) void *volume_key = NULL;
|
||||
struct fscrypt_policy policy = {};
|
||||
size_t volume_key_size = 0;
|
||||
const char *ip;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(setup);
|
||||
assert(user_record_storage(h) == USER_FSCRYPT);
|
||||
|
||||
assert_se(ip = user_record_image_path(h));
|
||||
|
||||
setup->root_fd = open(ip, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
|
||||
if (setup->root_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open home directory: %m");
|
||||
|
||||
if (ioctl(setup->root_fd, FS_IOC_GET_ENCRYPTION_POLICY, &policy) < 0) {
|
||||
if (errno == ENODATA)
|
||||
return log_error_errno(errno, "Home directory %s is not encrypted.", ip);
|
||||
if (ERRNO_IS_NOT_SUPPORTED(errno)) {
|
||||
log_error_errno(errno, "File system does not support fscrypt: %m");
|
||||
return -ENOLINK; /* make recognizable */
|
||||
}
|
||||
return log_error_errno(errno, "Failed to acquire encryption policy of %s: %m", ip);
|
||||
}
|
||||
|
||||
memcpy(setup->fscrypt_key_descriptor, policy.master_key_descriptor, FS_KEY_DESCRIPTOR_SIZE);
|
||||
|
||||
r = fscrypt_setup(
|
||||
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
|
||||
h->password,
|
||||
setup,
|
||||
&volume_key,
|
||||
&volume_key_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Also install the access key in the user's own keyring */
|
||||
|
||||
if (uid_is_valid(h->uid)) {
|
||||
r = safe_fork("(sd-addkey)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed install encryption key in user's keyring: %m");
|
||||
if (r == 0) {
|
||||
gid_t gid;
|
||||
|
||||
/* Child */
|
||||
|
||||
gid = user_record_gid(h);
|
||||
if (setresgid(gid, gid, gid) < 0) {
|
||||
log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid);
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (setgroups(0, NULL) < 0) {
|
||||
log_error_errno(errno, "Failed to reset auxiliary groups list: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (setresuid(h->uid, h->uid, h->uid) < 0) {
|
||||
log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", h->uid);
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = fscrypt_upload_volume_key(
|
||||
setup->fscrypt_key_descriptor,
|
||||
volume_key,
|
||||
volume_key_size,
|
||||
KEY_SPEC_USER_KEYRING);
|
||||
if (r < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fscrypt_slot_set(
|
||||
int root_fd,
|
||||
const void *volume_key,
|
||||
size_t volume_key_size,
|
||||
const char *password,
|
||||
uint32_t nr) {
|
||||
|
||||
_cleanup_free_ char *salt_base64 = NULL, *encrypted_base64 = NULL, *joined = NULL;
|
||||
char label[STRLEN("trusted.fscrypt_slot") + DECIMAL_STR_MAX(nr) + 1];
|
||||
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
|
||||
int r, encrypted_size_out1, encrypted_size_out2;
|
||||
uint8_t salt[64], derived[512 / 8] = {};
|
||||
_cleanup_free_ void *encrypted = NULL;
|
||||
const EVP_CIPHER *cc;
|
||||
size_t encrypted_size;
|
||||
|
||||
r = genuine_random_bytes(salt, sizeof(salt), RANDOM_BLOCK);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate salt: %m");
|
||||
|
||||
if (PKCS5_PBKDF2_HMAC(
|
||||
password, strlen(password),
|
||||
salt, sizeof(salt),
|
||||
0xFFFF, EVP_sha512(),
|
||||
sizeof(derived), derived) != 1) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
context = EVP_CIPHER_CTX_new();
|
||||
if (!context) {
|
||||
r = log_oom();
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* We use AES256 in counter mode */
|
||||
cc = EVP_aes_256_ctr();
|
||||
|
||||
/* We only use the first half of the derived key */
|
||||
assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc));
|
||||
|
||||
if (EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context.");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* Flush out the derived key now, we don't need it anymore */
|
||||
explicit_bzero_safe(derived, sizeof(derived));
|
||||
|
||||
encrypted_size = volume_key_size + EVP_CIPHER_key_length(cc) * 2;
|
||||
encrypted = malloc(encrypted_size);
|
||||
if (!encrypted)
|
||||
return log_oom();
|
||||
|
||||
if (EVP_EncryptUpdate(context, (uint8_t*) encrypted, &encrypted_size_out1, volume_key, volume_key_size) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt volume key.");
|
||||
|
||||
assert((size_t) encrypted_size_out1 <= encrypted_size);
|
||||
|
||||
if (EVP_EncryptFinal_ex(context, (uint8_t*) encrypted_size + encrypted_size_out1, &encrypted_size_out2) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish encryption of volume key.");
|
||||
|
||||
assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 < encrypted_size);
|
||||
encrypted_size = (size_t) encrypted_size_out1 + (size_t) encrypted_size_out2;
|
||||
|
||||
r = base64mem(salt, sizeof(salt), &salt_base64);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
r = base64mem(encrypted, encrypted_size, &encrypted_base64);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
joined = strjoin(salt_base64, ":", encrypted_base64);
|
||||
if (!joined)
|
||||
return log_oom();
|
||||
|
||||
xsprintf(label, "trusted.fscrypt_slot%" PRIu32, nr);
|
||||
if (fsetxattr(root_fd, label, joined, strlen(joined), 0) < 0)
|
||||
return log_error_errno(errno, "Failed to write xattr %s: %m", label);
|
||||
|
||||
log_info("Written key slot %s.", label);
|
||||
|
||||
return 0;
|
||||
|
||||
finish:
|
||||
explicit_bzero_safe(derived, sizeof(derived));
|
||||
return r;
|
||||
}
|
||||
|
||||
int home_create_fscrypt(
|
||||
UserRecord *h,
|
||||
char **effective_passwords,
|
||||
UserRecord **ret_home) {
|
||||
|
||||
_cleanup_(rm_rf_physical_and_freep) char *temporary = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
|
||||
_cleanup_(erase_and_freep) void *volume_key = NULL;
|
||||
struct fscrypt_policy policy = {};
|
||||
size_t volume_key_size = 512 / 8;
|
||||
_cleanup_close_ int root_fd = -1;
|
||||
_cleanup_free_ char *d = NULL;
|
||||
uint32_t nr = 0;
|
||||
const char *ip;
|
||||
char **i;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(user_record_storage(h) == USER_FSCRYPT);
|
||||
assert(ret_home);
|
||||
|
||||
assert_se(ip = user_record_image_path(h));
|
||||
|
||||
r = tempfn_random(ip, "homework", &d);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate temporary directory: %m");
|
||||
|
||||
(void) mkdir_parents(d, 0755);
|
||||
|
||||
if (mkdir(d, 0700) < 0)
|
||||
return log_error_errno(errno, "Failed to create temporary home directory %s: %m", d);
|
||||
|
||||
temporary = TAKE_PTR(d); /* Needs to be destroyed now */
|
||||
|
||||
root_fd = open(temporary, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
||||
if (root_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open temporary home directory: %m");
|
||||
|
||||
if (ioctl(root_fd, FS_IOC_GET_ENCRYPTION_POLICY, &policy) < 0) {
|
||||
if (ERRNO_IS_NOT_SUPPORTED(errno)) {
|
||||
log_error_errno(errno, "File system does not support fscrypt: %m");
|
||||
return -ENOLINK; /* make recognizable */
|
||||
}
|
||||
if (errno != ENODATA)
|
||||
return log_error_errno(errno, "Failed to get fscrypt policy of directory: %m");
|
||||
} else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Parent of %s already encrypted, refusing.", d);
|
||||
|
||||
volume_key = malloc(volume_key_size);
|
||||
if (!volume_key)
|
||||
return log_oom();
|
||||
|
||||
r = genuine_random_bytes(volume_key, volume_key_size, RANDOM_BLOCK);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire volume key: %m");
|
||||
|
||||
log_info("Generated volume key of size %zu.", volume_key_size);
|
||||
|
||||
policy = (struct fscrypt_policy) {
|
||||
.contents_encryption_mode = FS_ENCRYPTION_MODE_AES_256_XTS,
|
||||
.filenames_encryption_mode = FS_ENCRYPTION_MODE_AES_256_CTS,
|
||||
.flags = FS_POLICY_FLAGS_PAD_32,
|
||||
};
|
||||
|
||||
calculate_key_descriptor(volume_key, volume_key_size, policy.master_key_descriptor);
|
||||
|
||||
r = fscrypt_upload_volume_key(policy.master_key_descriptor, volume_key, volume_key_size, KEY_SPEC_THREAD_KEYRING);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_info("Uploaded volume key to kernel.");
|
||||
|
||||
if (ioctl(root_fd, FS_IOC_SET_ENCRYPTION_POLICY, &policy) < 0)
|
||||
return log_error_errno(errno, "Failed to set fscrypt policy on directory: %m");
|
||||
|
||||
log_info("Encryption policy set.");
|
||||
|
||||
STRV_FOREACH(i, effective_passwords) {
|
||||
r = fscrypt_slot_set(root_fd, volume_key, volume_key_size, *i, nr);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
nr++;
|
||||
}
|
||||
|
||||
(void) home_update_quota_classic(h, temporary);
|
||||
|
||||
r = home_populate(h, root_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_sync_and_statfs(root_fd, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET, &new_home);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to clone record: %m");
|
||||
|
||||
r = user_record_add_binding(
|
||||
new_home,
|
||||
USER_FSCRYPT,
|
||||
ip,
|
||||
SD_ID128_NULL,
|
||||
SD_ID128_NULL,
|
||||
SD_ID128_NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
UINT64_MAX,
|
||||
NULL,
|
||||
NULL,
|
||||
h->uid,
|
||||
(gid_t) h->uid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add binding to record: %m");
|
||||
|
||||
if (rename(temporary, ip) < 0)
|
||||
return log_error_errno(errno, "Failed to rename %s to %s: %m", temporary, ip);
|
||||
|
||||
temporary = mfree(temporary);
|
||||
|
||||
log_info("Everything completed.");
|
||||
|
||||
*ret_home = TAKE_PTR(new_home);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int home_passwd_fscrypt(
|
||||
UserRecord *h,
|
||||
HomeSetup *setup,
|
||||
char **pkcs11_decrypted_passwords, /* the passwords acquired via PKCS#11 security tokens */
|
||||
char **effective_passwords /* new passwords */) {
|
||||
|
||||
_cleanup_(erase_and_freep) void *volume_key = NULL;
|
||||
_cleanup_free_ char *xattr_buf = NULL;
|
||||
size_t volume_key_size = 0;
|
||||
uint32_t slot = 0;
|
||||
const char *xa;
|
||||
char **p;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(user_record_storage(h) == USER_FSCRYPT);
|
||||
assert(setup);
|
||||
|
||||
r = fscrypt_setup(
|
||||
pkcs11_decrypted_passwords,
|
||||
h->password,
|
||||
setup,
|
||||
&volume_key,
|
||||
&volume_key_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH(p, effective_passwords) {
|
||||
r = fscrypt_slot_set(setup->root_fd, volume_key, volume_key_size, *p, slot);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
slot++;
|
||||
}
|
||||
|
||||
r = flistxattr_malloc(setup->root_fd, &xattr_buf);
|
||||
if (r < 0)
|
||||
return log_error_errno(errno, "Failed to retrieve xattr list: %m");
|
||||
|
||||
NULSTR_FOREACH(xa, xattr_buf) {
|
||||
const char *nr;
|
||||
uint32_t z;
|
||||
|
||||
/* Check if this xattr has the format 'trusted.fscrypt_slot<nr>' where '<nr>' is a 32bit unsigned integer */
|
||||
nr = startswith(xa, "trusted.fscrypt_slot");
|
||||
if (!nr)
|
||||
continue;
|
||||
if (safe_atou32(nr, &z) < 0)
|
||||
continue;
|
||||
|
||||
if (z < slot)
|
||||
continue;
|
||||
|
||||
if (fremovexattr(setup->root_fd, xa) < 0)
|
||||
|
||||
if (errno != ENODATA)
|
||||
log_warning_errno(errno, "Failed to remove xattr %s: %m", xa);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "homework.h"
|
||||
#include "user-record.h"
|
||||
|
||||
int home_prepare_fscrypt(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup);
|
||||
int home_create_fscrypt(UserRecord *h, char **effective_passwords, UserRecord **ret_home);
|
||||
|
||||
int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,38 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "crypt-util.h"
|
||||
#include "homework.h"
|
||||
#include "user-record.h"
|
||||
|
||||
int home_prepare_luks(UserRecord *h, bool already_activated, const char *force_image_path, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_luks_home);
|
||||
|
||||
int home_activate_luks(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
|
||||
int home_deactivate_luks(UserRecord *h);
|
||||
|
||||
int home_store_header_identity_luks(UserRecord *h, HomeSetup *setup, UserRecord *old_home);
|
||||
|
||||
int home_create_luks(UserRecord *h, char **pkcs11_decrypted_passwords, char **effective_passwords, UserRecord **ret_home);
|
||||
|
||||
int home_validate_update_luks(UserRecord *h, HomeSetup *setup);
|
||||
|
||||
int home_resize_luks(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
|
||||
|
||||
int home_passwd_luks(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
|
||||
|
||||
int home_lock_luks(UserRecord *h);
|
||||
int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords);
|
||||
|
||||
static inline uint64_t luks_volume_key_size_convert(struct crypt_device *cd) {
|
||||
int k;
|
||||
|
||||
assert(cd);
|
||||
|
||||
/* Convert the "int" to uint64_t, which we usually use for byte sizes stored on disk. */
|
||||
|
||||
k = crypt_get_volume_key_size(cd);
|
||||
if (k <= 0)
|
||||
return UINT64_MAX;
|
||||
|
||||
return (uint64_t) k;
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <sched.h>
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "homework-mount.h"
|
||||
#include "mkdir.h"
|
||||
#include "mount-util.h"
|
||||
#include "path-util.h"
|
||||
#include "string-util.h"
|
||||
|
||||
static const char *mount_options_for_fstype(const char *fstype) {
|
||||
if (streq(fstype, "ext4"))
|
||||
return "noquota,user_xattr";
|
||||
if (streq(fstype, "xfs"))
|
||||
return "noquota";
|
||||
if (streq(fstype, "btrfs"))
|
||||
return "noacl";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int home_mount_node(const char *node, const char *fstype, bool discard) {
|
||||
_cleanup_free_ char *joined = NULL;
|
||||
const char *options, *discard_option;
|
||||
int r;
|
||||
|
||||
options = mount_options_for_fstype(fstype);
|
||||
|
||||
discard_option = discard ? "discard" : "nodiscard";
|
||||
|
||||
if (options) {
|
||||
joined = strjoin(options, ",", discard_option);
|
||||
if (!joined)
|
||||
return log_oom();
|
||||
|
||||
options = joined;
|
||||
} else
|
||||
options = discard_option;
|
||||
|
||||
r = mount_verbose(LOG_ERR, node, "/run/systemd/user-home-mount", fstype, MS_NODEV|MS_NOSUID|MS_RELATIME, strempty(options));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_info("Mounting file system completed.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int home_unshare_and_mount(const char *node, const char *fstype, bool discard) {
|
||||
int r;
|
||||
|
||||
if (unshare(CLONE_NEWNS) < 0)
|
||||
return log_error_errno(errno, "Couldn't unshare file system namespace: %m");
|
||||
|
||||
r = mount_verbose(LOG_ERR, "/run", "/run", NULL, MS_SLAVE|MS_REC, NULL); /* Mark /run as MS_SLAVE in our new namespace */
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(void) mkdir_p("/run/systemd/user-home-mount", 0700);
|
||||
|
||||
if (node)
|
||||
return home_mount_node(node, fstype, discard);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int home_move_mount(const char *user_name_and_realm, const char *target) {
|
||||
_cleanup_free_ char *subdir = NULL;
|
||||
const char *d;
|
||||
int r;
|
||||
|
||||
assert(user_name_and_realm);
|
||||
assert(target);
|
||||
|
||||
if (user_name_and_realm) {
|
||||
subdir = path_join("/run/systemd/user-home-mount/", user_name_and_realm);
|
||||
if (!subdir)
|
||||
return log_oom();
|
||||
|
||||
d = subdir;
|
||||
} else
|
||||
d = "/run/systemd/user-home-mount/";
|
||||
|
||||
(void) mkdir_p(target, 0700);
|
||||
|
||||
r = mount_verbose(LOG_ERR, d, target, NULL, MS_BIND, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = umount_verbose("/run/systemd/user-home-mount");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_info("Moving to final mount point %s completed.", target);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
int home_mount_node(const char *node, const char *fstype, bool discard);
|
||||
int home_unshare_and_mount(const char *node, const char *fstype, bool discard);
|
||||
int home_move_mount(const char *user_name_and_realm, const char *target);
|
|
@ -0,0 +1,104 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "hexdecoct.h"
|
||||
#include "homework-pkcs11.h"
|
||||
#include "pkcs11-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
int pkcs11_callback(
|
||||
CK_FUNCTION_LIST *m,
|
||||
CK_SESSION_HANDLE session,
|
||||
CK_SLOT_ID slot_id,
|
||||
const CK_SLOT_INFO *slot_info,
|
||||
const CK_TOKEN_INFO *token_info,
|
||||
P11KitUri *uri,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
|
||||
struct pkcs11_callback_data *data = userdata;
|
||||
_cleanup_free_ char *token_label = NULL;
|
||||
CK_TOKEN_INFO updated_token_info;
|
||||
size_t decrypted_key_size;
|
||||
CK_OBJECT_HANDLE object;
|
||||
char **i;
|
||||
CK_RV rv;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(slot_info);
|
||||
assert(token_info);
|
||||
assert(uri);
|
||||
assert(data);
|
||||
|
||||
/* Special return values:
|
||||
*
|
||||
* -ENOANO → if we need a PIN but have none
|
||||
* -ERFKILL → if a "protected authentication path" is needed but we have no OK to use it
|
||||
* -EOWNERDEAD → if the PIN is locked
|
||||
* -ENOLCK → if the supplied PIN is incorrect
|
||||
* -ETOOMANYREFS → ditto, but only a few tries left
|
||||
* -EUCLEAN → ditto, but only a single try left
|
||||
*/
|
||||
|
||||
token_label = pkcs11_token_label(token_info);
|
||||
if (!token_label)
|
||||
return log_oom();
|
||||
|
||||
if (FLAGS_SET(token_info->flags, CKF_PROTECTED_AUTHENTICATION_PATH)) {
|
||||
|
||||
if (data->secret->pkcs11_protected_authentication_path_permitted <= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ERFKILL), "Security token requires authentication through protected authentication path.");
|
||||
|
||||
rv = m->C_Login(session, CKU_USER, NULL, 0);
|
||||
if (rv != CKR_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to log into security token '%s': %s", token_label, p11_kit_strerror(rv));
|
||||
|
||||
log_info("Successully logged into security token '%s' via protected authentication path.", token_label);
|
||||
goto decrypt;
|
||||
}
|
||||
|
||||
if (!FLAGS_SET(token_info->flags, CKF_LOGIN_REQUIRED)) {
|
||||
log_info("No login into security token '%s' required.", token_label);
|
||||
goto decrypt;
|
||||
}
|
||||
|
||||
if (strv_isempty(data->secret->pkcs11_pin))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Security Token requires PIN.");
|
||||
|
||||
STRV_FOREACH(i, data->secret->pkcs11_pin) {
|
||||
rv = m->C_Login(session, CKU_USER, (CK_UTF8CHAR*) *i, strlen(*i));
|
||||
if (rv == CKR_OK) {
|
||||
log_info("Successfully logged into security token '%s' with PIN.", token_label);
|
||||
goto decrypt;
|
||||
}
|
||||
if (rv == CKR_PIN_LOCKED)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD), "PIN of security token is blocked. Please unblock it first.");
|
||||
if (!IN_SET(rv, CKR_PIN_INCORRECT, CKR_PIN_LEN_RANGE))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to log into security token '%s': %s", token_label, p11_kit_strerror(rv));
|
||||
}
|
||||
|
||||
rv = m->C_GetTokenInfo(slot_id, &updated_token_info);
|
||||
if (rv != CKR_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire updated security token information for slot %lu: %s", slot_id, p11_kit_strerror(rv));
|
||||
|
||||
if (FLAGS_SET(updated_token_info.flags, CKF_USER_PIN_FINAL_TRY))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "PIN of security token incorrect, only a single try left.");
|
||||
if (FLAGS_SET(updated_token_info.flags, CKF_USER_PIN_COUNT_LOW))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ETOOMANYREFS), "PIN of security token incorrect, only a few tries left.");
|
||||
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOLCK), "PIN of security token incorrect.");
|
||||
|
||||
decrypt:
|
||||
r = pkcs11_token_find_private_key(m, session, uri, &object);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pkcs11_token_decrypt_data(m, session, object, data->encrypted_key->data, data->encrypted_key->size, &decrypted_key, &decrypted_key_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (base64mem(decrypted_key, decrypted_key_size, &data->decrypted_password) < 0)
|
||||
return log_oom();
|
||||
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#if HAVE_P11KIT
|
||||
#include "memory-util.h"
|
||||
#include "user-record.h"
|
||||
#include "pkcs11-util.h"
|
||||
|
||||
struct pkcs11_callback_data {
|
||||
UserRecord *user_record;
|
||||
UserRecord *secret;
|
||||
Pkcs11EncryptedKey *encrypted_key;
|
||||
char *decrypted_password;
|
||||
};
|
||||
|
||||
static inline void pkcs11_callback_data_release(struct pkcs11_callback_data *data) {
|
||||
erase_and_free(data->decrypted_password);
|
||||
}
|
||||
|
||||
int pkcs11_callback(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slot_id, const CK_SLOT_INFO *slot_info, const CK_TOKEN_INFO *token_info, P11KitUri *uri, void *userdata);
|
||||
#endif
|
|
@ -0,0 +1,124 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#include <sys/quota.h>
|
||||
|
||||
#include "blockdev-util.h"
|
||||
#include "btrfs-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "format-util.h"
|
||||
#include "homework-quota.h"
|
||||
#include "missing_magic.h"
|
||||
#include "quota-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "user-util.h"
|
||||
|
||||
int home_update_quota_btrfs(UserRecord *h, const char *path) {
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(path);
|
||||
|
||||
if (h->disk_size == UINT64_MAX)
|
||||
return 0;
|
||||
|
||||
/* If the user wants quota, enable it */
|
||||
r = btrfs_quota_enable(path, true);
|
||||
if (r == -ENOTTY)
|
||||
return log_error_errno(r, "No btrfs quota support on subvolume %s.", path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enable btrfs quota support on %s.", path);
|
||||
|
||||
r = btrfs_qgroup_set_limit(path, 0, h->disk_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Faled to set disk quota on subvolume %s: %m", path);
|
||||
|
||||
log_info("Set btrfs quota.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int home_update_quota_classic(UserRecord *h, const char *path) {
|
||||
struct dqblk req;
|
||||
dev_t devno;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(uid_is_valid(h->uid));
|
||||
assert(path);
|
||||
|
||||
if (h->disk_size == UINT64_MAX)
|
||||
return 0;
|
||||
|
||||
r = get_block_device(path, &devno);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine block device of %s: %m", path);
|
||||
if (devno == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system %s not backed by a block device.", path);
|
||||
|
||||
r = quotactl_devno(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), devno, h->uid, &req);
|
||||
if (r < 0) {
|
||||
if (ERRNO_IS_NOT_SUPPORTED(r))
|
||||
return log_error_errno(r, "No UID quota support on %s.", path);
|
||||
|
||||
if (r != -ESRCH)
|
||||
return log_error_errno(r, "Failed to query disk quota for UID " UID_FMT ": %m", h->uid);
|
||||
|
||||
zero(req);
|
||||
} else {
|
||||
/* Shortcut things if everything is set up properly already */
|
||||
if (FLAGS_SET(req.dqb_valid, QIF_BLIMITS) && h->disk_size / QIF_DQBLKSIZE == req.dqb_bhardlimit) {
|
||||
log_info("Configured quota already matches the intended setting, not updating quota.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
req.dqb_valid = QIF_BLIMITS;
|
||||
req.dqb_bsoftlimit = req.dqb_bhardlimit = h->disk_size / QIF_DQBLKSIZE;
|
||||
|
||||
r = quotactl_devno(QCMD_FIXED(Q_SETQUOTA, USRQUOTA), devno, h->uid, &req);
|
||||
if (r < 0) {
|
||||
if (r == -ESRCH)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "UID quota not available on %s.", path);
|
||||
|
||||
return log_error_errno(r, "Failed to set disk quota for UID " UID_FMT ": %m", h->uid);
|
||||
}
|
||||
|
||||
log_info("Updated per-UID quota.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int home_update_quota_auto(UserRecord *h, const char *path) {
|
||||
struct statfs sfs;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
|
||||
if (h->disk_size == UINT64_MAX)
|
||||
return 0;
|
||||
|
||||
if (!path) {
|
||||
path = user_record_image_path(h);
|
||||
if (!path)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home record lacks image path.");
|
||||
}
|
||||
|
||||
if (statfs(path, &sfs) < 0)
|
||||
return log_error_errno(errno, "Failed to statfs() file system: %m");
|
||||
|
||||
if (is_fs_type(&sfs, XFS_SB_MAGIC) ||
|
||||
is_fs_type(&sfs, EXT4_SUPER_MAGIC))
|
||||
return home_update_quota_classic(h, path);
|
||||
|
||||
if (is_fs_type(&sfs, BTRFS_SUPER_MAGIC)) {
|
||||
|
||||
r = btrfs_is_subvol(path);
|
||||
if (r < 0)
|
||||
return log_error_errno(errno, "Failed to test if %s is a subvolume: %m", path);
|
||||
if (r == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Directory %s is not a subvolume, cannot apply quota.", path);
|
||||
|
||||
return home_update_quota_btrfs(h, path);
|
||||
}
|
||||
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Type of directory %s not known, cannot apply quota.", path);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "user-record.h"
|
||||
|
||||
int home_update_quota_btrfs(UserRecord *h, const char *path);
|
||||
int home_update_quota_classic(UserRecord *h, const char *path);
|
||||
int home_update_quota_auto(UserRecord *h, const char *path);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,57 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <sys/vfs.h>
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "loop-util.h"
|
||||
#include "user-record.h"
|
||||
#include "user-record-util.h"
|
||||
|
||||
typedef struct HomeSetup {
|
||||
char *dm_name;
|
||||
char *dm_node;
|
||||
|
||||
LoopDevice *loop;
|
||||
struct crypt_device *crypt_device;
|
||||
int root_fd;
|
||||
sd_id128_t found_partition_uuid;
|
||||
sd_id128_t found_luks_uuid;
|
||||
sd_id128_t found_fs_uuid;
|
||||
|
||||
uint8_t fscrypt_key_descriptor[FS_KEY_DESCRIPTOR_SIZE];
|
||||
|
||||
void *volume_key;
|
||||
size_t volume_key_size;
|
||||
|
||||
bool undo_dm;
|
||||
bool undo_mount;
|
||||
|
||||
uint64_t partition_offset;
|
||||
uint64_t partition_size;
|
||||
} HomeSetup;
|
||||
|
||||
#define HOME_SETUP_INIT \
|
||||
{ \
|
||||
.root_fd = -1, \
|
||||
.partition_offset = UINT64_MAX, \
|
||||
.partition_size = UINT64_MAX, \
|
||||
}
|
||||
|
||||
int home_setup_undo(HomeSetup *setup);
|
||||
|
||||
int home_prepare(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_header_home);
|
||||
|
||||
int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, char ***pkcs11_decrypted_passwords, struct statfs *ret_statfs, UserRecord **ret_new_home);
|
||||
|
||||
int home_populate(UserRecord *h, int dir_fd);
|
||||
|
||||
int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, char ***pkcs11_decrypted_passwords, UserRecord **ret_embedded_home, UserRecord **ret_new_home);
|
||||
int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, UserRecord *old_home);
|
||||
int home_extend_embedded_identity(UserRecord *h, UserRecord *used, HomeSetup *setup);
|
||||
|
||||
int user_record_authenticate(UserRecord *h, UserRecord *secret, char ***pkcs11_decrypted_passwords);
|
||||
|
||||
int home_sync_and_statfs(int root_fd, struct statfs *ret);
|
|
@ -0,0 +1,81 @@
|
|||
# SPDX-License-Identifier: LGPL-2.1+
|
||||
|
||||
systemd_homework_sources = files('''
|
||||
home-util.c
|
||||
home-util.h
|
||||
homework-cifs.c
|
||||
homework-cifs.h
|
||||
homework-directory.c
|
||||
homework-directory.h
|
||||
homework-fscrypt.c
|
||||
homework-fscrypt.h
|
||||
homework-luks.c
|
||||
homework-luks.h
|
||||
homework-mount.c
|
||||
homework-mount.h
|
||||
homework-pkcs11.h
|
||||
homework-quota.c
|
||||
homework-quota.h
|
||||
homework.c
|
||||
homework.h
|
||||
user-record-util.c
|
||||
user-record-util.h
|
||||
'''.split())
|
||||
|
||||
if conf.get('HAVE_P11KIT') == 1
|
||||
systemd_homework_sources += files('homework-pkcs11.c')
|
||||
endif
|
||||
|
||||
systemd_homed_sources = files('''
|
||||
home-util.c
|
||||
home-util.h
|
||||
homed-bus.c
|
||||
homed-bus.h
|
||||
homed-home-bus.c
|
||||
homed-home-bus.h
|
||||
homed-home.c
|
||||
homed-home.h
|
||||
homed-manager-bus.c
|
||||
homed-manager-bus.h
|
||||
homed-manager.c
|
||||
homed-manager.h
|
||||
homed-operation.c
|
||||
homed-operation.h
|
||||
homed-varlink.c
|
||||
homed-varlink.h
|
||||
homed.c
|
||||
pwquality-util.c
|
||||
pwquality-util.h
|
||||
user-record-sign.c
|
||||
user-record-sign.h
|
||||
user-record-util.c
|
||||
user-record-util.h
|
||||
'''.split())
|
||||
|
||||
homectl_sources = files('''
|
||||
home-util.c
|
||||
home-util.h
|
||||
homectl.c
|
||||
pwquality-util.c
|
||||
pwquality-util.h
|
||||
user-record-util.c
|
||||
user-record-util.h
|
||||
'''.split())
|
||||
|
||||
pam_systemd_home_sym = 'src/home/pam_systemd_home.sym'
|
||||
pam_systemd_home_c = files('''
|
||||
home-util.c
|
||||
home-util.h
|
||||
pam_systemd_home.c
|
||||
user-record-util.c
|
||||
user-record-util.h
|
||||
'''.split())
|
||||
|
||||
if conf.get('ENABLE_HOMED') == 1
|
||||
install_data('org.freedesktop.home1.conf',
|
||||
install_dir : dbuspolicydir)
|
||||
install_data('org.freedesktop.home1.service',
|
||||
install_dir : dbussystemservicedir)
|
||||
install_data('org.freedesktop.home1.policy',
|
||||
install_dir : polkitpolicydir)
|
||||
endif
|
|
@ -0,0 +1,193 @@
|
|||
<?xml version="1.0"?> <!--*-nxml-*-->
|
||||
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
|
||||
|
||||
<busconfig>
|
||||
|
||||
<policy user="root">
|
||||
<allow own="org.freedesktop.home1"/>
|
||||
<allow send_destination="org.freedesktop.home1"/>
|
||||
<allow receive_sender="org.freedesktop.home1"/>
|
||||
</policy>
|
||||
|
||||
<policy context="default">
|
||||
<deny send_destination="org.freedesktop.home1"/>
|
||||
|
||||
<!-- generic interfaces -->
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.DBus.Introspectable"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.DBus.Peer"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.DBus.Properties"
|
||||
send_member="Get"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.DBus.Properties"
|
||||
send_member="GetAll"/>
|
||||
|
||||
<!-- Manager object -->
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="GetHomeByName"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="GetHomeByUID"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="GetUserRecordByName"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="GetUserRecordByUID"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="ListHomes"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="ActivateHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="DeactivateHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="RegisterHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="UnregisterHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="CreateHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="RealizeHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="RemoveHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="FixateHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="AuthenticateHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="UpdateHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="ResizeHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="ChangePasswordHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="LockHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="UnlockHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="AcquireHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="RefHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="ReleaseHome"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Manager"
|
||||
send_member="LockAllHomes"/>
|
||||
|
||||
<!-- Home object -->
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Activate"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Deactivate"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Unregister"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Realize"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Remove"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Fixate"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Authenticate"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Update"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Resize"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="ChangePassword"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Lock"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Unlock"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Acquire"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Ref"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.home1"
|
||||
send_interface="org.freedesktop.home1.Home"
|
||||
send_member="Release"/>
|
||||
|
||||
<allow receive_sender="org.freedesktop.home1"/>
|
||||
</policy>
|
||||
|
||||
</busconfig>
|
|
@ -0,0 +1,72 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
|
||||
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
|
||||
|
||||
<policyconfig>
|
||||
|
||||
<vendor>The systemd Project</vendor>
|
||||
<vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
|
||||
|
||||
<action id="org.freedesktop.home1.create-home">
|
||||
<description gettext-domain="systemd">Create a home</description>
|
||||
<message gettext-domain="systemd">Authentication is required for creating a user's home.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="org.freedesktop.home1.remove-home">
|
||||
<description gettext-domain="systemd">Remove a home</description>
|
||||
<message gettext-domain="systemd">Authentication is required for removing a user's home.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="org.freedesktop.home1.authenticate-home">
|
||||
<description gettext-domain="systemd">Check credentials of a home</description>
|
||||
<message gettext-domain="systemd">Authentication is required for checking credentials against a user's home.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="org.freedesktop.home1.update-home">
|
||||
<description gettext-domain="systemd">Update a home</description>
|
||||
<message gettext-domain="systemd">Authentication is required for updating a user's home.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="org.freedesktop.home1.resize-home">
|
||||
<description gettext-domain="systemd">Resize a home</description>
|
||||
<message gettext-domain="systemd">Authentication is required for resizing a user's home.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="org.freedesktop.home1.passwd-home">
|
||||
<description gettext-domain="systemd">Change password of a home</description>
|
||||
<message gettext-domain="systemd">Authentication is required for changing the password of a user's home.</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin_keep</allow_any>
|
||||
<allow_inactive>auth_admin_keep</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
</policyconfig>
|
|
@ -0,0 +1,7 @@
|
|||
# SPDX-License-Identifier: LGPL-2.1+
|
||||
|
||||
[D-BUS Service]
|
||||
Name=org.freedesktop.home1
|
||||
Exec=/bin/false
|
||||
User=root
|
||||
SystemdService=dbus-org.freedesktop.home1.service
|
|
@ -0,0 +1,951 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <security/pam_ext.h>
|
||||
#include <security/pam_modules.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "bus-common-errors.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "home-util.h"
|
||||
#include "memory-util.h"
|
||||
#include "pam-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "strv.h"
|
||||
#include "user-record-util.h"
|
||||
#include "user-record.h"
|
||||
#include "user-util.h"
|
||||
|
||||
/* Used for the "systemd-user-record-is-homed" PAM data field, to indicate whether we know whether this user
|
||||
* record is managed by homed or by something else. */
|
||||
#define USER_RECORD_IS_HOMED INT_TO_PTR(1)
|
||||
#define USER_RECORD_IS_OTHER INT_TO_PTR(2)
|
||||
|
||||
static int parse_argv(
|
||||
pam_handle_t *handle,
|
||||
int argc, const char **argv,
|
||||
bool *please_suspend,
|
||||
bool *debug) {
|
||||
|
||||
int i;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argc == 0 || argv);
|
||||
|
||||
for (i = 0; i < argc; i++) {
|
||||
const char *v;
|
||||
|
||||
if ((v = startswith(argv[1], "suspend="))) {
|
||||
int k;
|
||||
|
||||
k = parse_boolean(v);
|
||||
if (k < 0)
|
||||
pam_syslog(handle, LOG_WARNING, "Failed to parse suspend-please= argument, ignoring: %s", v);
|
||||
else if (please_suspend)
|
||||
*please_suspend = k;
|
||||
|
||||
} else if ((v = startswith(argv[i], "debug="))) {
|
||||
int k;
|
||||
|
||||
k = parse_boolean(v);
|
||||
if (k < 0)
|
||||
pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", v);
|
||||
else if (debug)
|
||||
*debug = k;
|
||||
|
||||
} else
|
||||
pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acquire_user_record(
|
||||
pam_handle_t *handle,
|
||||
UserRecord **ret_record) {
|
||||
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
||||
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
|
||||
const char *username = NULL, *json = NULL;
|
||||
const void *b = NULL;
|
||||
int r;
|
||||
|
||||
assert(handle);
|
||||
|
||||
r = pam_get_user(handle, &username, NULL);
|
||||
if (r != PAM_SUCCESS) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get user name: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
if (isempty(username)) {
|
||||
pam_syslog(handle, LOG_ERR, "User name not set.");
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
/* Let's bypass all IPC complexity for the two user names we know for sure we don't manage, and for
|
||||
* user names we don't consider valid. */
|
||||
if (STR_IN_SET(username, "root", NOBODY_USER_NAME) || !valid_user_group_name(username))
|
||||
return PAM_USER_UNKNOWN;
|
||||
|
||||
/* Let's check if a previous run determined that this user is not managed by homed. If so, let's exit early */
|
||||
r = pam_get_data(handle, "systemd-user-record-is-homed", &b);
|
||||
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) {
|
||||
/* Failure */
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get PAM user record is homed flag: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
} else if (b == NULL)
|
||||
/* Nothing cached yet, need to acquire fresh */
|
||||
json = NULL;
|
||||
else if (b != USER_RECORD_IS_HOMED)
|
||||
/* Definitely not a homed record */
|
||||
return PAM_USER_UNKNOWN;
|
||||
else {
|
||||
/* It's a homed record, let's use the cache, so that we can share it between the session and
|
||||
* the authentication hooks */
|
||||
r = pam_get_data(handle, "systemd-user-record", (const void**) &json);
|
||||
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get PAM user record data: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (!json) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_free_ char *json_copy = NULL;
|
||||
|
||||
r = pam_acquire_bus_connection(handle, &bus);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = sd_bus_call_method(
|
||||
bus,
|
||||
"org.freedesktop.home1",
|
||||
"/org/freedesktop/home1",
|
||||
"org.freedesktop.home1.Manager",
|
||||
"GetUserRecordByName",
|
||||
&error,
|
||||
&reply,
|
||||
"s",
|
||||
username);
|
||||
if (r < 0) {
|
||||
if (sd_bus_error_has_name(&error, SD_BUS_ERROR_SERVICE_UNKNOWN) ||
|
||||
sd_bus_error_has_name(&error, SD_BUS_ERROR_NAME_HAS_NO_OWNER)) {
|
||||
pam_syslog(handle, LOG_DEBUG, "systemd-homed is not available: %s", bus_error_message(&error, r));
|
||||
goto user_unknown;
|
||||
}
|
||||
|
||||
if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_HOME)) {
|
||||
pam_syslog(handle, LOG_DEBUG, "Not a user managed by systemd-homed: %s", bus_error_message(&error, r));
|
||||
goto user_unknown;
|
||||
}
|
||||
|
||||
pam_syslog(handle, LOG_ERR, "Failed to query user record: %s", bus_error_message(&error, r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
r = sd_bus_message_read(reply, "sbo", &json, NULL, NULL);
|
||||
if (r < 0)
|
||||
return pam_bus_log_parse_error(handle, r);
|
||||
|
||||
json_copy = strdup(json);
|
||||
if (!json_copy)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = pam_set_data(handle, "systemd-user-record", json_copy, pam_cleanup_free);
|
||||
if (r != PAM_SUCCESS) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set PAM user record data: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
TAKE_PTR(json_copy);
|
||||
|
||||
r = pam_set_data(handle, "systemd-user-record-is-homed", USER_RECORD_IS_HOMED, NULL);
|
||||
if (r != PAM_SUCCESS) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set PAM user record is homed flag: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
r = json_parse(json, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to parse JSON user record: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
ur = user_record_new();
|
||||
if (!ur)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to load user record: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
if (!streq_ptr(username, ur->user_name)) {
|
||||
pam_syslog(handle, LOG_ERR, "Acquired user record does not match user name.");
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
if (ret_record)
|
||||
*ret_record = TAKE_PTR(ur);
|
||||
|
||||
return PAM_SUCCESS;
|
||||
|
||||
user_unknown:
|
||||
/* Cache this, so that we don't check again */
|
||||
r = pam_set_data(handle, "systemd-user-record-is-homed", USER_RECORD_IS_OTHER, NULL);
|
||||
if (r != PAM_SUCCESS)
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set PAM user record is homed flag, ignoring: %s", pam_strerror(handle, r));
|
||||
|
||||
return PAM_USER_UNKNOWN;
|
||||
}
|
||||
|
||||
static int release_user_record(pam_handle_t *handle) {
|
||||
int r, k;
|
||||
|
||||
r = pam_set_data(handle, "systemd-user-record", NULL, NULL);
|
||||
if (r != PAM_SUCCESS)
|
||||
pam_syslog(handle, LOG_ERR, "Failed to release PAM user record data: %s", pam_strerror(handle, r));
|
||||
|
||||
k = pam_set_data(handle, "systemd-user-record-is-homed", NULL, NULL);
|
||||
if (k != PAM_SUCCESS)
|
||||
pam_syslog(handle, LOG_ERR, "Failed to release PAM user record is homed flag: %s", pam_strerror(handle, k));
|
||||
|
||||
return IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA) ? k : r;
|
||||
}
|
||||
|
||||
static void cleanup_home_fd(pam_handle_t *handle, void *data, int error_status) {
|
||||
safe_close(PTR_TO_FD(data));
|
||||
}
|
||||
|
||||
static int handle_generic_user_record_error(
|
||||
pam_handle_t *handle,
|
||||
const char *user_name,
|
||||
UserRecord *secret,
|
||||
int ret,
|
||||
const sd_bus_error *error) {
|
||||
|
||||
assert(user_name);
|
||||
assert(secret);
|
||||
assert(error);
|
||||
|
||||
int r;
|
||||
|
||||
/* Logs about all errors, except for PAM_CONV_ERR, i.e. when requesting more info failed. */
|
||||
|
||||
if (sd_bus_error_has_name(error, BUS_ERROR_HOME_ABSENT)) {
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Home of user %s is currently absent, please plug in the necessary storage device or backing file system.", user_name);
|
||||
pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
|
||||
return PAM_PERM_DENIED;
|
||||
|
||||
} else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT)) {
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Too frequent unsuccessful login attempts for user %s, try again later.", user_name);
|
||||
pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
|
||||
return PAM_MAXTRIES;
|
||||
|
||||
} else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD)) {
|
||||
_cleanup_(erase_and_freep) char *newp = NULL;
|
||||
|
||||
/* This didn't work? Ask for an (additional?) password */
|
||||
|
||||
if (strv_isempty(secret->password))
|
||||
r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Password: ");
|
||||
else
|
||||
r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Password incorrect or not sufficient for authentication of user %s, please try again: ", user_name);
|
||||
if (r != PAM_SUCCESS)
|
||||
return PAM_CONV_ERR; /* no logging here */
|
||||
|
||||
if (isempty(newp)) {
|
||||
pam_syslog(handle, LOG_DEBUG, "Password request aborted.");
|
||||
return PAM_AUTHTOK_ERR;
|
||||
}
|
||||
|
||||
r = user_record_set_password(secret, STRV_MAKE(newp), true);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to store password: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
} else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN)) {
|
||||
_cleanup_(erase_and_freep) char *newp = NULL;
|
||||
|
||||
if (strv_isempty(secret->password))
|
||||
r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Security token of user %s not inserted, please enter password: ", user_name);
|
||||
else
|
||||
r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Password incorrect or not sufficient, and configured security token of user %s not inserted, please enter password: ", user_name);
|
||||
if (r != PAM_SUCCESS)
|
||||
return PAM_CONV_ERR; /* no logging here */
|
||||
|
||||
if (isempty(newp)) {
|
||||
pam_syslog(handle, LOG_DEBUG, "Password request aborted.");
|
||||
return PAM_AUTHTOK_ERR;
|
||||
}
|
||||
|
||||
r = user_record_set_password(secret, STRV_MAKE(newp), true);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to store password: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_NEEDED)) {
|
||||
_cleanup_(erase_and_freep) char *newp = NULL;
|
||||
|
||||
r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Please enter security token PIN: ");
|
||||
if (r != PAM_SUCCESS)
|
||||
return PAM_CONV_ERR; /* no logging here */
|
||||
|
||||
if (isempty(newp)) {
|
||||
pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
|
||||
return PAM_AUTHTOK_ERR;
|
||||
}
|
||||
|
||||
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED)) {
|
||||
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Please authenticate physically on security token of user %s.", user_name);
|
||||
|
||||
r = user_record_set_pkcs11_protected_authentication_path_permitted(secret, true);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set PKCS#11 protected authentication path permitted flag: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
|
||||
_cleanup_(erase_and_freep) char *newp = NULL;
|
||||
|
||||
r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Security token PIN incorrect, please enter PIN for security token of user %s again: ", user_name);
|
||||
if (r != PAM_SUCCESS)
|
||||
return PAM_CONV_ERR; /* no logging here */
|
||||
|
||||
if (isempty(newp)) {
|
||||
pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
|
||||
return PAM_AUTHTOK_ERR;
|
||||
}
|
||||
|
||||
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT)) {
|
||||
_cleanup_(erase_and_freep) char *newp = NULL;
|
||||
|
||||
r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Security token PIN incorrect (only a few tries left!), please enter PIN for security token of user %s again: ", user_name);
|
||||
if (r != PAM_SUCCESS)
|
||||
return PAM_CONV_ERR; /* no logging here */
|
||||
|
||||
if (isempty(newp)) {
|
||||
pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
|
||||
return PAM_AUTHTOK_ERR;
|
||||
}
|
||||
|
||||
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT)) {
|
||||
_cleanup_(erase_and_freep) char *newp = NULL;
|
||||
|
||||
r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Security token PIN incorrect (only one try left!), please enter PIN for security token of user %s again: ", user_name);
|
||||
if (r != PAM_SUCCESS)
|
||||
return PAM_CONV_ERR; /* no logging here */
|
||||
|
||||
if (isempty(newp)) {
|
||||
pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
|
||||
return PAM_AUTHTOK_ERR;
|
||||
}
|
||||
|
||||
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
} else {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
static int acquire_home(
|
||||
pam_handle_t *handle,
|
||||
bool please_authenticate,
|
||||
bool please_suspend,
|
||||
bool debug) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL, *secret = NULL;
|
||||
bool do_auth = please_authenticate, home_not_active = false, home_locked = false;
|
||||
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_close_ int acquired_fd = -1;
|
||||
const void *home_fd_ptr = NULL;
|
||||
unsigned n_attempts = 0;
|
||||
int r;
|
||||
|
||||
assert(handle);
|
||||
|
||||
/* This acquires a reference to a home directory in one of two ways: if please_authenticate is true,
|
||||
* then we'll call AcquireHome() after asking the user for a password. Otherwise it tries to call
|
||||
* RefHome() and if that fails queries the user for a password and uses AcquireHome().
|
||||
*
|
||||
* The idea is that the PAM authentication hook sets please_authenticate and thus always
|
||||
* authenticates, while the other PAM hooks unset it so that they can a ref of their own without
|
||||
* authentication if possible, but with authentication if necessary. */
|
||||
|
||||
/* If we already have acquired the fd, let's shortcut this */
|
||||
r = pam_get_data(handle, "systemd-home-fd", &home_fd_ptr);
|
||||
if (r == PAM_SUCCESS && PTR_TO_INT(home_fd_ptr) >= 0)
|
||||
return PAM_SUCCESS;
|
||||
|
||||
r = pam_acquire_bus_connection(handle, &bus);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = acquire_user_record(handle, &ur);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
/* Implement our own retry loop here instead of relying on the PAM client's one. That's because it
|
||||
* might happen that the the record we stored on the host does not match the encryption password of
|
||||
* the LUKS image in case the image was used in a different system where the password was
|
||||
* changed. In that case it will happen that the LUKS password and the host password are
|
||||
* different, and we handle that by collecting and passing multiple passwords in that case. Hence we
|
||||
* treat bad passwords as a request to collect one more password and pass the new all all previously
|
||||
* used passwords again. */
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
|
||||
if (do_auth && !secret) {
|
||||
const char *cached_password = NULL;
|
||||
|
||||
secret = user_record_new();
|
||||
if (!secret)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
/* If there's already a cached password, use it. But if not let's authenticate
|
||||
* without anything, maybe some other authentication mechanism systemd-homed
|
||||
* implements (such as PKCS#11) allows us to authenticate without anything else. */
|
||||
r = pam_get_item(handle, PAM_AUTHTOK, (const void**) &cached_password);
|
||||
if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get cached password: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!isempty(cached_password)) {
|
||||
r = user_record_set_password(secret, STRV_MAKE(cached_password), true);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to store password: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r = sd_bus_message_new_method_call(
|
||||
bus,
|
||||
&m,
|
||||
"org.freedesktop.home1",
|
||||
"/org/freedesktop/home1",
|
||||
"org.freedesktop.home1.Manager",
|
||||
do_auth ? "AcquireHome" : "RefHome");
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
r = sd_bus_message_append(m, "s", ur->user_name);
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
if (do_auth) {
|
||||
r = bus_message_append_secret(m, secret);
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
}
|
||||
|
||||
r = sd_bus_message_append(m, "b", please_suspend);
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
|
||||
if (r < 0) {
|
||||
|
||||
if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_NOT_ACTIVE))
|
||||
/* Only on RefHome(): We can't access the home directory currently, unless
|
||||
* it's unlocked with a password. Hence, let's try this again, this time with
|
||||
* authentication. */
|
||||
home_not_active = true;
|
||||
else if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_LOCKED))
|
||||
home_locked = true; /* Similar */
|
||||
else {
|
||||
r = handle_generic_user_record_error(handle, ur->user_name, secret, r, &error);
|
||||
if (r == PAM_CONV_ERR) {
|
||||
/* Password/PIN prompts will fail in certain environments, for example when
|
||||
* we are called from OpenSSH's account or session hooks, or in systemd's
|
||||
* per-service PAM logic. In that case, print a friendly message and accept
|
||||
* failure. */
|
||||
|
||||
if (home_not_active)
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Home of user %s is currently not active, please log in locally first.", ur->user_name);
|
||||
if (home_locked)
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Home of user %s is currently locked, please unlock locally first.", ur->user_name);
|
||||
|
||||
pam_syslog(handle, please_authenticate ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt.");
|
||||
|
||||
return home_not_active || home_locked ? PAM_PERM_DENIED : PAM_CONV_ERR;
|
||||
}
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
}
|
||||
|
||||
} else {
|
||||
int fd;
|
||||
|
||||
r = sd_bus_message_read(reply, "h", &fd);
|
||||
if (r < 0)
|
||||
return pam_bus_log_parse_error(handle, r);
|
||||
|
||||
acquired_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
|
||||
if (acquired_fd < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to duplicate acquired fd: %s", bus_error_message(&error, r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (++n_attempts >= 5) {
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Too many unsuccessful login attempts for user %s, refusing.", ur->user_name);
|
||||
pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", ur->user_name, bus_error_message(&error, r));
|
||||
return PAM_MAXTRIES;
|
||||
}
|
||||
|
||||
/* Try again, this time with authentication if we didn't do that before. */
|
||||
do_auth = true;
|
||||
}
|
||||
|
||||
r = pam_set_data(handle, "systemd-home-fd", FD_TO_PTR(acquired_fd), cleanup_home_fd);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set PAM bus data: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
TAKE_FD(acquired_fd);
|
||||
|
||||
if (do_auth) {
|
||||
/* We likely just activated the home directory, let's flush out the user record, since a
|
||||
* newer embedded user record might have been acquired from the activation. */
|
||||
|
||||
r = release_user_record(handle);
|
||||
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
|
||||
return r;
|
||||
}
|
||||
|
||||
pam_syslog(handle, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name);
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
static int release_home_fd(pam_handle_t *handle) {
|
||||
const void *home_fd_ptr = NULL;
|
||||
int r;
|
||||
|
||||
r = pam_get_data(handle, "systemd-home-fd", &home_fd_ptr);
|
||||
if (r == PAM_NO_MODULE_DATA || PTR_TO_FD(home_fd_ptr) < 0)
|
||||
return PAM_NO_MODULE_DATA;
|
||||
|
||||
r = pam_set_data(handle, "systemd-home-fd", NULL, NULL);
|
||||
if (r != PAM_SUCCESS)
|
||||
pam_syslog(handle, LOG_ERR, "Failed to release PAM home reference fd: %s", pam_strerror(handle, r));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_authenticate(
|
||||
pam_handle_t *handle,
|
||||
int flags,
|
||||
int argc, const char **argv) {
|
||||
|
||||
bool debug = false, suspend_please = false;
|
||||
|
||||
if (parse_argv(handle,
|
||||
argc, argv,
|
||||
&suspend_please,
|
||||
&debug) < 0)
|
||||
return PAM_AUTH_ERR;
|
||||
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed authenticating");
|
||||
|
||||
return acquire_home(handle, /* please_authenticate= */ true, suspend_please, debug);
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_open_session(
|
||||
pam_handle_t *handle,
|
||||
int flags,
|
||||
int argc, const char **argv) {
|
||||
|
||||
bool debug = false, suspend_please = false;
|
||||
int r;
|
||||
|
||||
if (parse_argv(handle,
|
||||
argc, argv,
|
||||
&suspend_please,
|
||||
&debug) < 0)
|
||||
return PAM_SESSION_ERR;
|
||||
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed session start");
|
||||
|
||||
r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug);
|
||||
if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */
|
||||
return PAM_SUCCESS;
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = pam_putenv(handle, "SYSTEMD_HOME=1");
|
||||
if (r != PAM_SUCCESS) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set PAM environment variable $SYSTEMD_HOME: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Let's release the D-Bus connection, after all the session might live quite a long time, and we are
|
||||
* not going to process the bus connection in that time, so let's better close before the daemon
|
||||
* kicks us off because we are not processing anything. */
|
||||
(void) pam_release_bus_connection(handle);
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_close_session(
|
||||
pam_handle_t *handle,
|
||||
int flags,
|
||||
int argc, const char **argv) {
|
||||
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
|
||||
const char *username = NULL;
|
||||
bool debug = false;
|
||||
int r;
|
||||
|
||||
if (parse_argv(handle,
|
||||
argc, argv,
|
||||
NULL,
|
||||
&debug) < 0)
|
||||
return PAM_SESSION_ERR;
|
||||
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed session end");
|
||||
|
||||
/* Let's explicitly drop the reference to the homed session, so that the subsequent ReleaseHome()
|
||||
* call will be able to do its thing. */
|
||||
r = release_home_fd(handle);
|
||||
if (r == PAM_NO_MODULE_DATA) /* Nothing to do, we never acquired an fd */
|
||||
return PAM_SUCCESS;
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = pam_get_user(handle, &username, NULL);
|
||||
if (r != PAM_SUCCESS) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get user name: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
r = pam_acquire_bus_connection(handle, &bus);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_new_method_call(
|
||||
bus,
|
||||
&m,
|
||||
"org.freedesktop.home1",
|
||||
"/org/freedesktop/home1",
|
||||
"org.freedesktop.home1.Manager",
|
||||
"ReleaseHome");
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
r = sd_bus_message_append(m, "s", username);
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, 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_HOME_BUSY))
|
||||
pam_syslog(handle, LOG_NOTICE, "Not deactivating home directory of %s, as it is still used.", username);
|
||||
else {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to release user home: %s", bus_error_message(&error, r));
|
||||
return PAM_SESSION_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_acct_mgmt(
|
||||
pam_handle_t *handle,
|
||||
int flags,
|
||||
int argc,
|
||||
const char **argv) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
||||
bool debug = false, please_suspend = false;
|
||||
usec_t t;
|
||||
int r;
|
||||
|
||||
if (parse_argv(handle,
|
||||
argc, argv,
|
||||
&please_suspend,
|
||||
&debug) < 0)
|
||||
return PAM_AUTH_ERR;
|
||||
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed account management");
|
||||
|
||||
r = acquire_home(handle, /* please_authenticate = */ false, please_suspend, debug);
|
||||
if (r == PAM_USER_UNKNOWN)
|
||||
return PAM_SUCCESS; /* we don't have anything to say about users we don't manage */
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = acquire_user_record(handle, &ur);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = user_record_test_blocked(ur);
|
||||
switch (r) {
|
||||
|
||||
case -ESTALE:
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record is newer than current system time, prohibiting access.");
|
||||
return PAM_ACCT_EXPIRED;
|
||||
|
||||
case -ENOLCK:
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record is blocked, prohibiting access.");
|
||||
return PAM_ACCT_EXPIRED;
|
||||
|
||||
case -EL2HLT:
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record is not valid yet, prohibiting access.");
|
||||
return PAM_ACCT_EXPIRED;
|
||||
|
||||
case -EL3HLT:
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record is not valid anymore, prohibiting access.");
|
||||
return PAM_ACCT_EXPIRED;
|
||||
|
||||
default:
|
||||
if (r < 0) {
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record not valid, prohibiting access.");
|
||||
return PAM_ACCT_EXPIRED;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
t = user_record_ratelimit_next_try(ur);
|
||||
if (t != USEC_INFINITY) {
|
||||
usec_t n = now(CLOCK_REALTIME);
|
||||
|
||||
if (t > n) {
|
||||
char buf[FORMAT_TIMESPAN_MAX];
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Too many logins, try again in %s.",
|
||||
format_timespan(buf, sizeof(buf), t - n, USEC_PER_SEC));
|
||||
|
||||
return PAM_MAXTRIES;
|
||||
}
|
||||
}
|
||||
|
||||
r = user_record_test_password_change_required(ur);
|
||||
switch (r) {
|
||||
|
||||
case -EKEYREVOKED:
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Password change required.");
|
||||
return PAM_NEW_AUTHTOK_REQD;
|
||||
|
||||
case -EOWNERDEAD:
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Password expired, change requird.");
|
||||
return PAM_NEW_AUTHTOK_REQD;
|
||||
|
||||
case -EKEYREJECTED:
|
||||
/* Strictly speaking this is only about password expiration, and we might want to allow
|
||||
* authentication via PKCS#11 or so, but let's ignore this fine distinction for now. */
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Password is expired, but can't change, refusing login.");
|
||||
return PAM_AUTHTOK_EXPIRED;
|
||||
|
||||
case -EKEYEXPIRED:
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Password will expire soon, please change.");
|
||||
break;
|
||||
|
||||
case -EROFS:
|
||||
/* All good, just means the password if we wanted to change we couldn't, but we don't need to */
|
||||
break;
|
||||
|
||||
default:
|
||||
if (r < 0) {
|
||||
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record not valid, prohibiting access.");
|
||||
return PAM_AUTHTOK_EXPIRED;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_chauthtok(
|
||||
pam_handle_t *handle,
|
||||
int flags,
|
||||
int argc,
|
||||
const char **argv) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL, *old_secret = NULL, *new_secret = NULL;
|
||||
const char *old_password = NULL, *new_password = NULL;
|
||||
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
|
||||
unsigned n_attempts = 0;
|
||||
bool debug = false;
|
||||
int r;
|
||||
|
||||
if (parse_argv(handle,
|
||||
argc, argv,
|
||||
NULL,
|
||||
&debug) < 0)
|
||||
return PAM_AUTH_ERR;
|
||||
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed account management");
|
||||
|
||||
r = pam_acquire_bus_connection(handle, &bus);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = acquire_user_record(handle, &ur);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
/* Start with cached credentials */
|
||||
r = pam_get_item(handle, PAM_OLDAUTHTOK, (const void**) &old_password);
|
||||
if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get old password: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
r = pam_get_item(handle, PAM_AUTHTOK, (const void**) &new_password);
|
||||
if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get cached password: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
if (isempty(new_password)) {
|
||||
/* No, it's not cached, then let's ask for the password and its verification, and cache
|
||||
* it. */
|
||||
|
||||
r = pam_get_authtok_noverify(handle, &new_password, "New password: ");
|
||||
if (r != PAM_SUCCESS) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get new password: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
if (isempty(new_password)) {
|
||||
pam_syslog(handle, LOG_DEBUG, "Password request aborted.");
|
||||
return PAM_AUTHTOK_ERR;
|
||||
}
|
||||
|
||||
r = pam_get_authtok_verify(handle, &new_password, "new password: "); /* Lower case, since PAM prefixes 'Repeat' */
|
||||
if (r != PAM_SUCCESS) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get password again: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
// FIXME: pam_pwquality will ask for the password a third time. It really shouldn't do
|
||||
// that, and instead assume the password was already verified once when it is found to be
|
||||
// cached already. needs to be fixed in pam_pwquality
|
||||
}
|
||||
|
||||
/* Now everything is cached and checked, let's exit from the preliminary check */
|
||||
if (FLAGS_SET(flags, PAM_PRELIM_CHECK))
|
||||
return PAM_SUCCESS;
|
||||
|
||||
|
||||
old_secret = user_record_new();
|
||||
if (!old_secret)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
if (!isempty(old_password)) {
|
||||
r = user_record_set_password(old_secret, STRV_MAKE(old_password), true);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to store old password: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
new_secret = user_record_new();
|
||||
if (!new_secret)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = user_record_set_password(new_secret, STRV_MAKE(new_password), true);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to store new password: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
|
||||
r = sd_bus_message_new_method_call(
|
||||
bus,
|
||||
&m,
|
||||
"org.freedesktop.home1",
|
||||
"/org/freedesktop/home1",
|
||||
"org.freedesktop.home1.Manager",
|
||||
"ChangePasswordHome");
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
r = sd_bus_message_append(m, "s", ur->user_name);
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
r = bus_message_append_secret(m, new_secret);
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
r = bus_message_append_secret(m, old_secret);
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
|
||||
if (r < 0) {
|
||||
r = handle_generic_user_record_error(handle, ur->user_name, old_secret, r, &error);
|
||||
if (r == PAM_CONV_ERR) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to prompt for password/prompt.");
|
||||
return PAM_CONV_ERR;
|
||||
}
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
} else {
|
||||
pam_syslog(handle, LOG_NOTICE, "Successfully changed password for user %s.", ur->user_name);
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
if (++n_attempts >= 5)
|
||||
break;
|
||||
|
||||
/* Try again */
|
||||
};
|
||||
|
||||
pam_syslog(handle, LOG_NOTICE, "Failed to change password for user %s: %m", ur->user_name);
|
||||
return PAM_MAXTRIES;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
{
|
||||
global:
|
||||
pam_sm_authenticate;
|
||||
pam_sm_setcred;
|
||||
pam_sm_open_session;
|
||||
pam_sm_close_session;
|
||||
pam_sm_acct_mgmt;
|
||||
pam_sm_chauthtok;
|
||||
local: *;
|
||||
};
|
|
@ -0,0 +1,186 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#if HAVE_PWQUALITY
|
||||
/* pwquality.h uses size_t but doesn't include sys/types.h on its own */
|
||||
#include <sys/types.h>
|
||||
#include <pwquality.h>
|
||||
#endif
|
||||
|
||||
#include "bus-common-errors.h"
|
||||
#include "home-util.h"
|
||||
#include "memory-util.h"
|
||||
#include "pwquality-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
#if HAVE_PWQUALITY
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(pwquality_settings_t*, pwquality_free_settings);
|
||||
|
||||
static void pwquality_maybe_disable_dictionary(
|
||||
pwquality_settings_t *pwq) {
|
||||
|
||||
char buf[PWQ_MAX_ERROR_MESSAGE_LEN];
|
||||
const char *path;
|
||||
int r;
|
||||
|
||||
r = pwquality_get_str_value(pwq, PWQ_SETTING_DICT_PATH, &path);
|
||||
if (r < 0) {
|
||||
log_warning("Failed to read libpwquality dictionary path, ignoring: %s", pwquality_strerror(buf, sizeof(buf), r, NULL));
|
||||
return;
|
||||
}
|
||||
|
||||
// REMOVE THIS AS SOON AS https://github.com/libpwquality/libpwquality/pull/21 IS MERGED AND RELEASED
|
||||
if (isempty(path))
|
||||
path = "/usr/share/cracklib/pw_dict.pwd.gz";
|
||||
|
||||
if (isempty(path)) {
|
||||
log_warning("Weird, no dictionary file configured, ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (access(path, F_OK) >= 0)
|
||||
return;
|
||||
|
||||
if (errno != ENOENT) {
|
||||
log_warning_errno(errno, "Failed to check if dictionary file %s exists, ignoring: %m", path);
|
||||
return;
|
||||
}
|
||||
|
||||
r = pwquality_set_int_value(pwq, PWQ_SETTING_DICT_CHECK, 0);
|
||||
if (r < 0) {
|
||||
log_warning("Failed to disable libpwquality dictionary check, ignoring: %s", pwquality_strerror(buf, sizeof(buf), r, NULL));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int quality_check_password(
|
||||
UserRecord *hr,
|
||||
UserRecord *secret,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(pwquality_free_settingsp) pwquality_settings_t *pwq = NULL;
|
||||
char buf[PWQ_MAX_ERROR_MESSAGE_LEN], **pp;
|
||||
void *auxerror;
|
||||
int r;
|
||||
|
||||
assert(hr);
|
||||
assert(secret);
|
||||
|
||||
pwq = pwquality_default_settings();
|
||||
if (!pwq)
|
||||
return log_oom();
|
||||
|
||||
r = pwquality_read_config(pwq, NULL, &auxerror);
|
||||
if (r < 0)
|
||||
log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to read libpwquality configuation, ignoring: %s",
|
||||
pwquality_strerror(buf, sizeof(buf), r, auxerror));
|
||||
|
||||
pwquality_maybe_disable_dictionary(pwq);
|
||||
|
||||
/* This is a bit more complex than one might think at first. pwquality_check() would like to know the
|
||||
* old password to make security checks. We support arbitrary numbers of passwords however, hence we
|
||||
* call the function once for each combination of old and new password. */
|
||||
|
||||
/* Iterate through all new passwords */
|
||||
STRV_FOREACH(pp, secret->password) {
|
||||
bool called = false;
|
||||
char **old;
|
||||
|
||||
r = test_password_many(hr->hashed_password, *pp);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) /* This is an old password as it isn't listed in the hashedPassword field, skip it */
|
||||
continue;
|
||||
|
||||
/* Check this password against all old passwords */
|
||||
STRV_FOREACH(old, secret->password) {
|
||||
|
||||
if (streq(*pp, *old))
|
||||
continue;
|
||||
|
||||
r = test_password_many(hr->hashed_password, *old);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) /* This is a new password, not suitable as old password */
|
||||
continue;
|
||||
|
||||
r = pwquality_check(pwq, *pp, *old, hr->user_name, &auxerror);
|
||||
if (r < 0)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_LOW_PASSWORD_QUALITY, "Password too weak: %s",
|
||||
pwquality_strerror(buf, sizeof(buf), r, auxerror));
|
||||
|
||||
called = true;
|
||||
}
|
||||
|
||||
if (called)
|
||||
continue;
|
||||
|
||||
/* If there are no old passwords, let's call pwquality_check() without any. */
|
||||
r = pwquality_check(pwq, *pp, NULL, hr->user_name, &auxerror);
|
||||
if (r < 0)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_LOW_PASSWORD_QUALITY, "Password too weak: %s",
|
||||
pwquality_strerror(buf, sizeof(buf), r, auxerror));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define N_SUGGESTIONS 6
|
||||
|
||||
int suggest_passwords(void) {
|
||||
_cleanup_(pwquality_free_settingsp) pwquality_settings_t *pwq = NULL;
|
||||
_cleanup_strv_free_erase_ char **suggestions = NULL;
|
||||
_cleanup_(erase_and_freep) char *joined = NULL;
|
||||
char buf[PWQ_MAX_ERROR_MESSAGE_LEN];
|
||||
void *auxerror;
|
||||
size_t i;
|
||||
int r;
|
||||
|
||||
pwq = pwquality_default_settings();
|
||||
if (!pwq)
|
||||
return log_oom();
|
||||
|
||||
r = pwquality_read_config(pwq, NULL, &auxerror);
|
||||
if (r < 0)
|
||||
log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to read libpwquality configuation, ignoring: %s",
|
||||
pwquality_strerror(buf, sizeof(buf), r, auxerror));
|
||||
|
||||
pwquality_maybe_disable_dictionary(pwq);
|
||||
|
||||
suggestions = new0(char*, N_SUGGESTIONS);
|
||||
if (!suggestions)
|
||||
return log_oom();
|
||||
|
||||
for (i = 0; i < N_SUGGESTIONS; i++) {
|
||||
r = pwquality_generate(pwq, 64, suggestions + i);
|
||||
if (r < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate password, ignoring: %s",
|
||||
pwquality_strerror(buf, sizeof(buf), r, NULL));
|
||||
}
|
||||
|
||||
joined = strv_join(suggestions, " ");
|
||||
if (!joined)
|
||||
return log_oom();
|
||||
|
||||
log_info("Password suggestions: %s", joined);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int quality_check_password(
|
||||
UserRecord *hr,
|
||||
UserRecord *secret,
|
||||
sd_bus_error *error) {
|
||||
|
||||
assert(hr);
|
||||
assert(secret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int suggest_passwords(void) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,9 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "sd-bus.h"
|
||||
#include "user-record.h"
|
||||
|
||||
int quality_check_password(UserRecord *hr, UserRecord *secret, sd_bus_error *error);
|
||||
|
||||
int suggest_passwords(void);
|
|
@ -0,0 +1,174 @@
|
|||
#include <openssl/pem.h>
|
||||
|
||||
#include "fd-util.h"
|
||||
#include "user-record-sign.h"
|
||||
#include "fileio.h"
|
||||
|
||||
static int user_record_signable_json(UserRecord *ur, char **ret) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *reduced = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *j = NULL;
|
||||
int r;
|
||||
|
||||
assert(ur);
|
||||
assert(ret);
|
||||
|
||||
r = user_record_clone(ur, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_STRIP_SECRET|USER_RECORD_STRIP_BINDING|USER_RECORD_STRIP_STATUS|USER_RECORD_STRIP_SIGNATURE, &reduced);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
j = json_variant_ref(reduced->json);
|
||||
|
||||
r = json_variant_normalize(&j);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return json_variant_format(j, 0, ret);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(EVP_MD_CTX*, EVP_MD_CTX_free);
|
||||
|
||||
int user_record_sign(UserRecord *ur, EVP_PKEY *private_key, UserRecord **ret) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *encoded = NULL, *v = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *signed_ur = NULL;
|
||||
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
|
||||
_cleanup_free_ char *text = NULL, *key = NULL;
|
||||
size_t signature_size = 0, key_size = 0;
|
||||
_cleanup_free_ void *signature = NULL;
|
||||
_cleanup_fclose_ FILE *mf = NULL;
|
||||
int r;
|
||||
|
||||
assert(ur);
|
||||
assert(private_key);
|
||||
assert(ret);
|
||||
|
||||
r = user_record_signable_json(ur, &text);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
md_ctx = EVP_MD_CTX_new();
|
||||
if (!md_ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
if (EVP_DigestSignInit(md_ctx, NULL, NULL, NULL, private_key) <= 0)
|
||||
return -EIO;
|
||||
|
||||
/* Request signature size */
|
||||
if (EVP_DigestSign(md_ctx, NULL, &signature_size, (uint8_t*) text, strlen(text)) <= 0)
|
||||
return -EIO;
|
||||
|
||||
signature = malloc(signature_size);
|
||||
if (!signature)
|
||||
return -ENOMEM;
|
||||
|
||||
if (EVP_DigestSign(md_ctx, signature, &signature_size, (uint8_t*) text, strlen(text)) <= 0)
|
||||
return -EIO;
|
||||
|
||||
mf = open_memstream_unlocked(&key, &key_size);
|
||||
if (!mf)
|
||||
return -ENOMEM;
|
||||
|
||||
if (PEM_write_PUBKEY(mf, private_key) <= 0)
|
||||
return -EIO;
|
||||
|
||||
r = fflush_and_check(mf);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = json_build(&encoded, JSON_BUILD_ARRAY(
|
||||
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(signature, signature_size)),
|
||||
JSON_BUILD_PAIR("key", JSON_BUILD_STRING(key)))));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
v = json_variant_ref(ur->json);
|
||||
|
||||
r = json_variant_set_field(&v, "signature", encoded);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (DEBUG_LOGGING)
|
||||
json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO, NULL, NULL);
|
||||
|
||||
signed_ur = user_record_new();
|
||||
if (!signed_ur)
|
||||
return log_oom();
|
||||
|
||||
r = user_record_load(signed_ur, v, USER_RECORD_LOAD_FULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(signed_ur);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int user_record_verify(UserRecord *ur, EVP_PKEY *public_key) {
|
||||
_cleanup_free_ char *text = NULL;
|
||||
unsigned n_good = 0, n_bad = 0;
|
||||
JsonVariant *array, *e;
|
||||
int r;
|
||||
|
||||
assert(ur);
|
||||
assert(public_key);
|
||||
|
||||
array = json_variant_by_key(ur->json, "signature");
|
||||
if (!array)
|
||||
return USER_RECORD_UNSIGNED;
|
||||
|
||||
if (!json_variant_is_array(array))
|
||||
return -EINVAL;
|
||||
|
||||
if (json_variant_elements(array) == 0)
|
||||
return USER_RECORD_UNSIGNED;
|
||||
|
||||
r = user_record_signable_json(ur, &text);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
JSON_VARIANT_ARRAY_FOREACH(e, array) {
|
||||
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
|
||||
_cleanup_free_ void *signature = NULL;
|
||||
size_t signature_size = 0;
|
||||
JsonVariant *data;
|
||||
|
||||
if (!json_variant_is_object(e))
|
||||
return -EINVAL;
|
||||
|
||||
data = json_variant_by_key(e, "data");
|
||||
if (!data)
|
||||
return -EINVAL;
|
||||
|
||||
r = json_variant_unbase64(data, &signature, &signature_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
md_ctx = EVP_MD_CTX_new();
|
||||
if (!md_ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
if (EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, public_key) <= 0)
|
||||
return -EIO;
|
||||
|
||||
if (EVP_DigestVerify(md_ctx, signature, signature_size, (uint8_t*) text, strlen(text)) <= 0) {
|
||||
n_bad ++;
|
||||
continue;
|
||||
}
|
||||
|
||||
n_good ++;
|
||||
}
|
||||
|
||||
return n_good > 0 ? (n_bad == 0 ? USER_RECORD_SIGNED_EXCLUSIVE : USER_RECORD_SIGNED) :
|
||||
(n_bad == 0 ? USER_RECORD_UNSIGNED : USER_RECORD_FOREIGN);
|
||||
}
|
||||
|
||||
int user_record_has_signature(UserRecord *ur) {
|
||||
JsonVariant *array;
|
||||
|
||||
array = json_variant_by_key(ur->json, "signature");
|
||||
if (!array)
|
||||
return false;
|
||||
|
||||
if (!json_variant_is_array(array))
|
||||
return -EINVAL;
|
||||
|
||||
return json_variant_elements(array) > 0;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "user-record.h"
|
||||
|
||||
int user_record_sign(UserRecord *ur, EVP_PKEY *private_key, UserRecord **ret);
|
||||
|
||||
enum {
|
||||
USER_RECORD_UNSIGNED, /* user record has no signature */
|
||||
USER_RECORD_SIGNED_EXCLUSIVE, /* user record has only a signature by our own key */
|
||||
USER_RECORD_SIGNED, /* user record is signed by us, but by others too */
|
||||
USER_RECORD_FOREIGN, /* user record is not signed by us, but by others */
|
||||
};
|
||||
|
||||
int user_record_verify(UserRecord *ur, EVP_PKEY *public_key);
|
||||
|
||||
int user_record_has_signature(UserRecord *ur);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,58 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "user-record.h"
|
||||
#include "group-record.h"
|
||||
|
||||
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);
|
||||
|
||||
typedef enum UserReconcileMode {
|
||||
USER_RECONCILE_ANY,
|
||||
USER_RECONCILE_REQUIRE_NEWER, /* host version must be newer than embedded version */
|
||||
USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, /* similar, but may also be equal */
|
||||
_USER_RECONCILE_MODE_MAX,
|
||||
_USER_RECONCILE_MODE_INVALID = -1,
|
||||
} UserReconcileMode;
|
||||
|
||||
enum { /* return values */
|
||||
USER_RECONCILE_HOST_WON,
|
||||
USER_RECONCILE_EMBEDDED_WON,
|
||||
USER_RECONCILE_IDENTICAL,
|
||||
};
|
||||
|
||||
int user_record_reconcile(UserRecord *host, UserRecord *embedded, UserReconcileMode mode, UserRecord **ret);
|
||||
int user_record_add_binding(UserRecord *h, UserStorage storage, const char *image_path, sd_id128_t partition_uuid, sd_id128_t luks_uuid, sd_id128_t fs_uuid, const char *luks_cipher, const char *luks_cipher_mode, uint64_t luks_volume_key_size, const char *file_system_type, const char *home_directory, uid_t uid, gid_t gid);
|
||||
|
||||
/* Results of the two test functions below. */
|
||||
enum {
|
||||
USER_TEST_UNDEFINED, /* Returned by user_record_test_image_path() if the storage type knows no image paths */
|
||||
USER_TEST_ABSENT,
|
||||
USER_TEST_EXISTS,
|
||||
USER_TEST_MOUNTED, /* Only applies to user_record_test_home_directory(), when the home directory exists. */
|
||||
USER_TEST_MAYBE, /* Only applies to LUKS devices: block device exists, but we don't know if it's the right one */
|
||||
};
|
||||
|
||||
int user_record_test_home_directory(UserRecord *h);
|
||||
int user_record_test_home_directory_and_warn(UserRecord *h);
|
||||
int user_record_test_image_path(UserRecord *h);
|
||||
int user_record_test_image_path_and_warn(UserRecord *h);
|
||||
|
||||
int user_record_test_secret(UserRecord *h, UserRecord *secret);
|
||||
|
||||
int user_record_update_last_changed(UserRecord *h, bool with_password);
|
||||
int user_record_set_disk_size(UserRecord *h, uint64_t disk_size);
|
||||
int user_record_set_password(UserRecord *h, char **password, bool prepend);
|
||||
int user_record_make_hashed_password(UserRecord *h, char **password, bool extend);
|
||||
int user_record_set_hashed_password(UserRecord *h, char **hashed_password);
|
||||
int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend);
|
||||
int user_record_set_pkcs11_protected_authentication_path_permitted(UserRecord *h, int b);
|
||||
int user_record_set_password_change_now(UserRecord *h, int b);
|
||||
int user_record_merge_secret(UserRecord *h, UserRecord *secret);
|
||||
int user_record_good_authentication(UserRecord *h);
|
||||
int user_record_bad_authentication(UserRecord *h);
|
||||
int user_record_ratelimit(UserRecord *h);
|
||||
|
||||
int user_record_is_supported(UserRecord *hr, sd_bus_error *error);
|
|
@ -105,5 +105,35 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
|
|||
SD_BUS_ERROR_MAP(BUS_ERROR_SPEED_METER_INACTIVE, EOPNOTSUPP),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_UNMANAGED_INTERFACE, EOPNOTSUPP),
|
||||
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_HOME, EEXIST),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_UID_IN_USE, EEXIST),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_USER_NAME_EXISTS, EEXIST),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_EXISTS, EEXIST),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ALREADY_ACTIVE, EALREADY),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ALREADY_FIXATED, EALREADY),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_UNFIXATED, EADDRNOTAVAIL),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_ACTIVE, EALREADY),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ABSENT, EREMOTE),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_BUSY, EBUSY),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PASSWORD, ENOKEY),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_LOW_PASSWORD_QUALITY, EUCLEAN),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN, EBADSLT),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_NEEDED, ENOANO),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, ERFKILL),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_LOCKED, EOWNERDEAD),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN, ENOLCK),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT, ETOOMANYREFS),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT, EUCLEAN),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_BAD_SIGNATURE, EKEYREJECTED),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_MISMATCH, EUCLEAN),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_DOWNGRADE, ESTALE),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_SIGNED, EROFS),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_BAD_HOME_SIZE, ERANGE),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRIVATE_KEY, ENOPKG),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_LOCKED, ENOEXEC),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_LOCKED, ENOEXEC),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOO_MANY_OPERATIONS, ENOBUFS),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_AUTHENTICATION_LIMIT_HIT, ETOOMANYREFS),
|
||||
|
||||
SD_BUS_ERROR_MAP_END
|
||||
};
|
||||
|
|
|
@ -84,4 +84,35 @@
|
|||
#define BUS_ERROR_SPEED_METER_INACTIVE "org.freedesktop.network1.SpeedMeterInactive"
|
||||
#define BUS_ERROR_UNMANAGED_INTERFACE "org.freedesktop.network1.UnmanagedInterface"
|
||||
|
||||
#define BUS_ERROR_NO_SUCH_HOME "org.freedesktop.home1.NoSuchHome"
|
||||
#define BUS_ERROR_UID_IN_USE "org.freedesktop.home1.UIDInUse"
|
||||
#define BUS_ERROR_USER_NAME_EXISTS "org.freedesktop.home1.UserNameExists"
|
||||
#define BUS_ERROR_HOME_EXISTS "org.freedesktop.home1.HomeExists"
|
||||
#define BUS_ERROR_HOME_ALREADY_ACTIVE "org.freedesktop.home1.HomeAlreadyActive"
|
||||
#define BUS_ERROR_HOME_ALREADY_FIXATED "org.freedesktop.home1.HomeAlreadyFixated"
|
||||
#define BUS_ERROR_HOME_UNFIXATED "org.freedesktop.home1.HomeUnfixated"
|
||||
#define BUS_ERROR_HOME_NOT_ACTIVE "org.freedesktop.home1.HomeNotActive"
|
||||
#define BUS_ERROR_HOME_ABSENT "org.freedesktop.home1.HomeAbsent"
|
||||
#define BUS_ERROR_HOME_BUSY "org.freedesktop.home1.HomeBusy"
|
||||
#define BUS_ERROR_BAD_PASSWORD "org.freedesktop.home1.BadPassword"
|
||||
#define BUS_ERROR_LOW_PASSWORD_QUALITY "org.freedesktop.home1.LowPasswordQuality"
|
||||
#define BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN "org.freedesktop.home1.BadPasswordAndNoToken"
|
||||
#define BUS_ERROR_TOKEN_PIN_NEEDED "org.freedesktop.home1.TokenPinNeeded"
|
||||
#define BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED "org.freedesktop.home1.TokenProtectedAuthenticationPathNeeded"
|
||||
#define BUS_ERROR_TOKEN_PIN_LOCKED "org.freedesktop.home1.TokenPinLocked"
|
||||
#define BUS_ERROR_TOKEN_BAD_PIN "org.freedesktop.home1.BadPin"
|
||||
#define BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT "org.freedesktop.home1.BadPinFewTriesLeft"
|
||||
#define BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT "org.freedesktop.home1.BadPinOneTryLeft"
|
||||
#define BUS_ERROR_BAD_SIGNATURE "org.freedesktop.home1.BadSignature"
|
||||
#define BUS_ERROR_HOME_RECORD_MISMATCH "org.freedesktop.home1.RecordMismatch"
|
||||
#define BUS_ERROR_HOME_RECORD_DOWNGRADE "org.freedesktop.home1.RecordDowngrade"
|
||||
#define BUS_ERROR_HOME_RECORD_SIGNED "org.freedesktop.home1.RecordSigned"
|
||||
#define BUS_ERROR_BAD_HOME_SIZE "org.freedesktop.home1.BadHomeSize"
|
||||
#define BUS_ERROR_NO_PRIVATE_KEY "org.freedesktop.home1.NoPrivateKey"
|
||||
#define BUS_ERROR_HOME_LOCKED "org.freedesktop.home1.HomeLocked"
|
||||
#define BUS_ERROR_HOME_NOT_LOCKED "org.freedesktop.home1.HomeNotLocked"
|
||||
#define BUS_ERROR_NO_DISK_SPACE "org.freedesktop.home1.NoDiskSpace"
|
||||
#define BUS_ERROR_TOO_MANY_OPERATIONS "org.freedesktop.home1.TooManyOperations"
|
||||
#define BUS_ERROR_AUTHENTICATION_LIMIT_HIT "org.freedesktop.home1.AuthenticationLimitHit"
|
||||
|
||||
BUS_ERROR_MAP_ELF_USE(bus_common_errors);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8)
|
||||
#define GPT_VAR SD_ID128_MAKE(4d,21,b0,16,b5,34,45,c2,a9,fb,5c,16,e0,91,fd,2d)
|
||||
#define GPT_TMP SD_ID128_MAKE(7e,c6,f5,57,3b,c5,4a,ca,b2,93,16,ef,5d,f6,39,d1)
|
||||
#define GPT_USER_HOME SD_ID128_MAKE(77,3f,91,ef,66,d4,49,b5,bd,83,d6,83,bf,40,ad,16)
|
||||
|
||||
/* Verity partitions for the root partitions above (we only define them for the root partitions, because only they are
|
||||
* are commonly read-only and hence suitable for verity). */
|
||||
|
|
|
@ -17,11 +17,12 @@
|
|||
#include "sd-messages.h"
|
||||
|
||||
#include "btrfs-util.h"
|
||||
#include "bus-error.h"
|
||||
#include "def.h"
|
||||
#include "exec-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "format-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "parse-util.h"
|
||||
|
@ -125,6 +126,49 @@ static int write_state(FILE **f, char **states) {
|
|||
return r;
|
||||
}
|
||||
|
||||
static int lock_all_homes(void) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
int r;
|
||||
|
||||
/* Let's synchronously lock all home directories managed by homed that have been marked for it. This
|
||||
* way the key material required to access these volumes is hopefully removed from memory. */
|
||||
|
||||
r = sd_bus_open_system(&bus);
|
||||
if (r < 0)
|
||||
return log_warning_errno(r, "Failed to connect to system bus, ignoring: %m");
|
||||
|
||||
r = sd_bus_message_new_method_call(
|
||||
bus,
|
||||
&m,
|
||||
"org.freedesktop.home1",
|
||||
"/org/freedesktop/home1",
|
||||
"org.freedesktop.home1.Manager",
|
||||
"LockAllHomes");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
/* If homed is not running it can't have any home directories active either. */
|
||||
r = sd_bus_message_set_auto_start(m, false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to disable auto-start of LockAllHomes() message: %m");
|
||||
|
||||
r = sd_bus_call(bus, m, DEFAULT_TIMEOUT_USEC, &error, NULL);
|
||||
if (r < 0) {
|
||||
if (sd_bus_error_has_name(&error, SD_BUS_ERROR_SERVICE_UNKNOWN) ||
|
||||
sd_bus_error_has_name(&error, SD_BUS_ERROR_NAME_HAS_NO_OWNER)) {
|
||||
log_debug("systemd-homed is not running, skipping locking of home directories.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return log_error_errno(r, "Failed to lock home directories: %s", bus_error_message(&error, r));
|
||||
}
|
||||
|
||||
log_debug("Successfully requested for all home directories to be locked.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int execute(char **modes, char **states) {
|
||||
char *arguments[] = {
|
||||
NULL,
|
||||
|
@ -166,6 +210,7 @@ static int execute(char **modes, char **states) {
|
|||
}
|
||||
|
||||
(void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
|
||||
(void) lock_all_homes();
|
||||
|
||||
log_struct(LOG_INFO,
|
||||
"MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../TEST-01-BASIC/Makefile
|
|
@ -0,0 +1,42 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
TEST_DESCRIPTION="testing homed"
|
||||
TEST_NO_QEMU=1
|
||||
|
||||
. $TEST_BASE_DIR/test-functions
|
||||
|
||||
test_setup() {
|
||||
create_empty_image
|
||||
mkdir -p $TESTDIR/root
|
||||
mount ${LOOPDEV}p1 $TESTDIR/root
|
||||
|
||||
(
|
||||
LOG_LEVEL=5
|
||||
eval $(udevadm info --export --query=env --name=${LOOPDEV}p2)
|
||||
|
||||
setup_basic_environment
|
||||
mask_supporting_services
|
||||
|
||||
# setup the testsuite service
|
||||
cat >$initdir/etc/systemd/system/testsuite.service <<EOF
|
||||
[Unit]
|
||||
Description=Testsuite service
|
||||
Before=getty-pre.target
|
||||
Wants=getty-pre.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/bin/bash -x /testsuite.sh
|
||||
Type=oneshot
|
||||
NotifyAccess=all
|
||||
EOF
|
||||
cp testsuite.sh $initdir/
|
||||
|
||||
setup_testsuite
|
||||
) || return 1
|
||||
setup_nspawn_root
|
||||
|
||||
ddebug "umount $TESTDIR/root"
|
||||
umount $TESTDIR/root
|
||||
}
|
||||
|
||||
do_test "$@"
|
|
@ -0,0 +1,74 @@
|
|||
#!/bin/bash
|
||||
set -ex
|
||||
set -o pipefail
|
||||
|
||||
# Check if homectl is installed, and if it isn't bail out early instead of failing
|
||||
if ! test -x /usr/bin/homectl ; then
|
||||
echo OK > /testok
|
||||
exit 0
|
||||
fi
|
||||
|
||||
inspect() {
|
||||
homectl inspect $1 | tee /tmp/a
|
||||
userdbctl user $1 | tee /tmp/b
|
||||
cmp /tmp/a /tmp/b
|
||||
rm /tmp/a /tmp/b
|
||||
}
|
||||
|
||||
systemd-analyze log-level debug
|
||||
systemd-analyze log-target console
|
||||
|
||||
NEWPASSWORD=xEhErW0ndafV4s homectl create test-user --disk-size=20M
|
||||
inspect test-user
|
||||
|
||||
PASSWORD=xEhErW0ndafV4s homectl authenticate test-user
|
||||
|
||||
PASSWORD=xEhErW0ndafV4s homectl activate test-user
|
||||
inspect test-user
|
||||
|
||||
PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Inline test"
|
||||
inspect test-user
|
||||
|
||||
homectl deactivate test-user
|
||||
inspect test-user
|
||||
|
||||
PASSWORD=xEhErW0ndafV4s NEWPASSWORD=yPN4N0fYNKUkOq homectl passwd test-user
|
||||
inspect test-user
|
||||
|
||||
PASSWORD=yPN4N0fYNKUkOq homectl activate test-user
|
||||
inspect test-user
|
||||
|
||||
SYSTEMD_LOG_LEVEL=debug PASSWORD=yPN4N0fYNKUkOq NEWPASSWORD=xEhErW0ndafV4s homectl passwd test-user
|
||||
inspect test-user
|
||||
|
||||
homectl deactivate test-user
|
||||
inspect test-user
|
||||
|
||||
PASSWORD=xEhErW0ndafV4s homectl activate test-user
|
||||
inspect test-user
|
||||
|
||||
PASSWORD=xEhErW0ndafV4s homectl deactivate test-user
|
||||
inspect test-user
|
||||
|
||||
PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Offline test"
|
||||
inspect test-user
|
||||
|
||||
PASSWORD=xEhErW0ndafV4s homectl activate test-user
|
||||
inspect test-user
|
||||
|
||||
PASSWORD=xEhErW0ndafV4s homectl deactivate test-user
|
||||
inspect test-user
|
||||
|
||||
! 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
|
||||
|
||||
homectl remove test-user
|
||||
|
||||
systemd-analyze log-level info
|
||||
|
||||
echo OK > /testok
|
||||
|
||||
exit 0
|
|
@ -19,6 +19,7 @@ ADDITIONAL_DEPS=(python3-libevdev
|
|||
python3-pyparsing
|
||||
clang
|
||||
perl
|
||||
libpwquality-dev
|
||||
libfdisk-dev
|
||||
libp11-kit-dev
|
||||
libssl-dev)
|
||||
|
|
|
@ -25,6 +25,7 @@ ADDITIONAL_DEPS=(dnf-plugins-core
|
|||
llvm
|
||||
perl
|
||||
libfdisk-devel
|
||||
libpwquality-devel
|
||||
openssl-devel
|
||||
p11-kit-devel)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ sudo apt-get update -y
|
|||
sudo apt-get build-dep systemd -y
|
||||
sudo apt-get install -y ninja-build python3-pip python3-setuptools quota
|
||||
# The following should be dropped when debian packaging has been updated to include them
|
||||
sudo apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev
|
||||
sudo apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev libpwquality-dev
|
||||
pip3 install meson
|
||||
|
||||
cd $REPO_ROOT
|
||||
|
|
|
@ -15,7 +15,7 @@ sudo apt-get update -y
|
|||
sudo apt-get build-dep systemd -y
|
||||
sudo apt-get install -y ninja-build python3-pip python3-setuptools
|
||||
# The following should be dropped when debian packaging has been updated to include them
|
||||
sudo apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev
|
||||
sudo apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev libpwquality-dev
|
||||
pip3 install meson
|
||||
|
||||
cd $REPO_ROOT
|
||||
|
|
|
@ -195,6 +195,8 @@ in_units = [
|
|||
['systemd-portabled.service', 'ENABLE_PORTABLED',
|
||||
'dbus-org.freedesktop.portable1.service'],
|
||||
['systemd-userdbd.service', 'ENABLE_USERDB'],
|
||||
['systemd-homed.service', 'ENABLE_HOMED',
|
||||
'multi-user.target.wants/ dbus-org.freedesktop.home1.service'],
|
||||
['systemd-quotacheck.service', 'ENABLE_QUOTACHECK'],
|
||||
['systemd-random-seed.service', 'ENABLE_RANDOMSEED',
|
||||
'sysinit.target.wants/'],
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# SPDX-License-Identifier: LGPL-2.1+
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Home Manager
|
||||
Documentation=man:systemd-homed.service(8)
|
||||
RequiresMountsFor=/home
|
||||
|
||||
[Service]
|
||||
BusName=org.freedesktop.home1
|
||||
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_FSETID CAP_SETGID CAP_SETUID
|
||||
DeviceAllow=/dev/loop-control rw
|
||||
DeviceAllow=/dev/mapper/control rw
|
||||
DeviceAllow=block-* rw
|
||||
ExecStart=@rootlibexecdir@/systemd-homed
|
||||
IPAddressDeny=any
|
||||
KillMode=mixed
|
||||
LimitNOFILE=@HIGH_RLIMIT_NOFILE@
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
PrivateNetwork=yes
|
||||
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_ALG
|
||||
RestrictNamespaces=mnt
|
||||
RestrictRealtime=yes
|
||||
StateDirectory=systemd/home
|
||||
SystemCallArchitectures=native
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallFilter=@system-service @mount
|
||||
@SERVICE_WATCHDOG@
|
Loading…
Reference in New Issue