home: add new systemd-homed service that can manage LUKS homes

Fixes more or less: https://bugs.freedesktop.org/show_bug.cgi?id=67474
This commit is contained in:
Lennart Poettering 2019-07-04 18:35:39 +02:00
parent e53db1405c
commit 70a5db5822
50 changed files with 15193 additions and 0 deletions

View File

@ -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,35 @@ 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)
endif
foreach alias : ['halt', 'poweroff', 'reboot', 'runlevel', 'shutdown', 'telinit']
meson.add_install_script(meson_make_symlink,
join_paths(rootbindir, 'systemctl'),
@ -3291,6 +3348,8 @@ missing = []
foreach tuple : [
['libcryptsetup'],
['PAM'],
['pwquality'],
['fdisk'],
['p11kit'],
['AUDIT'],
['IMA'],
@ -3329,6 +3388,7 @@ foreach tuple : [
['machined'],
['portabled'],
['userdb'],
['homed'],
['importd'],
['hostnamed'],
['timedated'],

View File

@ -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'],

160
src/home/home-util.c Normal file
View File

@ -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;
}

24
src/home/home-util.h Normal file
View File

@ -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);

64
src/home/homed-bus.c Normal file
View File

@ -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;
}

10
src/home/homed-bus.h Normal file
View File

@ -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);

877
src/home/homed-home-bus.c Normal file
View File

@ -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;
}

36
src/home/homed-home-bus.h Normal file
View File

@ -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);

2712
src/home/homed-home.c Normal file

File diff suppressed because it is too large Load Diff

168
src/home/homed-home.h Normal file
View File

@ -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);

View File

@ -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;
}

View File

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "sd-bus.h"
extern const sd_bus_vtable manager_vtable[];

1672
src/home/homed-manager.c Normal file

File diff suppressed because it is too large Load Diff

67
src/home/homed-manager.h Normal file
View File

@ -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);

View File

@ -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;
}
}

View File

@ -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);
}

370
src/home/homed-varlink.c Normal file
View File

@ -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);
}

8
src/home/homed-varlink.h Normal file
View File

@ -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);

46
src/home/homed.c Normal file
View File

@ -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);

215
src/home/homework-cifs.c Normal file
View File

@ -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;
}

11
src/home/homework-cifs.h Normal file
View File

@ -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);

View File

@ -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;
}

View File

@ -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);

644
src/home/homework-fscrypt.c Normal file
View File

@ -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;
}

View File

@ -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);

2954
src/home/homework-luks.c Normal file

File diff suppressed because it is too large Load Diff

38
src/home/homework-luks.h Normal file
View File

@ -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;
}

96
src/home/homework-mount.c Normal file
View File

@ -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;
}

View File

@ -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);

104
src/home/homework-pkcs11.c Normal file
View File

@ -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;
}

View File

@ -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

124
src/home/homework-quota.c Normal file
View File

@ -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);
}

View File

@ -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);

1482
src/home/homework.c Normal file

File diff suppressed because it is too large Load Diff

57
src/home/homework.h Normal file
View File

@ -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);

62
src/home/meson.build Normal file
View File

@ -0,0 +1,62 @@
# 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())
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

View File

@ -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>

View File

@ -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>

View File

@ -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

140
src/home/pwquality-util.c Normal file
View File

@ -0,0 +1,140 @@
/* 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 "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;
}
#else
int quality_check_password(
UserRecord *hr,
UserRecord *secret,
sd_bus_error *error) {
assert(hr);
assert(secret);
return 0;
}
#endif

View File

@ -0,0 +1,7 @@
/* 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);

174
src/home/user-record-sign.c Normal file
View File

@ -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;
}

View File

@ -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);

1225
src/home/user-record-util.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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
};

View File

@ -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);

View File

@ -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). */

View File

@ -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/'],

View File

@ -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@