mirror of
https://github.com/systemd/systemd.git
synced 2024-12-31 21:18:09 +03:00
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:
parent
e53db1405c
commit
70a5db5822
60
meson.build
60
meson.build
@ -243,6 +243,7 @@ conf.set_quoted('SYSTEMD_EXPORT_PATH', join_paths(rootlib
|
||||
conf.set_quoted('VENDOR_KEYRING_PATH', join_paths(rootlibexecdir, 'import-pubring.gpg'))
|
||||
conf.set_quoted('USER_KEYRING_PATH', join_paths(pkgsysconfdir, 'import-pubring.gpg'))
|
||||
conf.set_quoted('DOCUMENT_ROOT', join_paths(pkgdatadir, 'gatewayd'))
|
||||
conf.set_quoted('SYSTEMD_HOMEWORK_PATH', join_paths(rootlibexecdir, 'systemd-homework'))
|
||||
conf.set_quoted('SYSTEMD_USERWORK_PATH', join_paths(rootlibexecdir, 'systemd-userwork'))
|
||||
conf.set10('MEMORY_ACCOUNTING_DEFAULT', memory_accounting_default)
|
||||
conf.set_quoted('MEMORY_ACCOUNTING_DEFAULT_YES_NO', memory_accounting_default ? 'yes' : 'no')
|
||||
@ -884,6 +885,16 @@ else
|
||||
endif
|
||||
conf.set10('HAVE_LIBFDISK', have)
|
||||
|
||||
want_pwquality = get_option('pwquality')
|
||||
if want_pwquality != 'false' and not skip_deps
|
||||
libpwquality = dependency('pwquality', required : want_pwquality == 'true')
|
||||
have = libpwquality.found()
|
||||
else
|
||||
have = false
|
||||
libpwquality = []
|
||||
endif
|
||||
conf.set10('HAVE_PWQUALITY', have)
|
||||
|
||||
want_seccomp = get_option('seccomp')
|
||||
if want_seccomp != 'false' and not skip_deps
|
||||
libseccomp = dependency('libseccomp',
|
||||
@ -1011,6 +1022,9 @@ if want_libcryptsetup != 'false' and not skip_deps
|
||||
version : '>= 2.0.1',
|
||||
required : want_libcryptsetup == 'true')
|
||||
have = libcryptsetup.found()
|
||||
|
||||
conf.set10('HAVE_CRYPT_SET_METADATA_SIZE',
|
||||
have and cc.has_function('crypt_set_metadata_size', dependencies : libcryptsetup))
|
||||
else
|
||||
have = false
|
||||
libcryptsetup = []
|
||||
@ -1316,6 +1330,19 @@ else
|
||||
endif
|
||||
conf.set10('ENABLE_IMPORTD', have)
|
||||
|
||||
want_homed = get_option('homed')
|
||||
if want_homed != 'false'
|
||||
have = (conf.get('HAVE_OPENSSL') == 1 and
|
||||
conf.get('HAVE_LIBFDISK') == 1 and
|
||||
conf.get('HAVE_LIBCRYPTSETUP') == 1)
|
||||
if want_homed == 'true' and not have
|
||||
error('homed support was requested, but dependencies are not available')
|
||||
endif
|
||||
else
|
||||
have = false
|
||||
endif
|
||||
conf.set10('ENABLE_HOMED', have)
|
||||
|
||||
want_remote = get_option('remote')
|
||||
if want_remote != 'false'
|
||||
have_deps = [conf.get('HAVE_MICROHTTPD') == 1,
|
||||
@ -1564,6 +1591,7 @@ subdir('src/locale')
|
||||
subdir('src/machine')
|
||||
subdir('src/portable')
|
||||
subdir('src/userdb')
|
||||
subdir('src/home')
|
||||
subdir('src/nspawn')
|
||||
subdir('src/resolve')
|
||||
subdir('src/timedate')
|
||||
@ -2034,6 +2062,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'],
|
||||
|
@ -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
160
src/home/home-util.c
Normal 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
24
src/home/home-util.h
Normal 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
64
src/home/homed-bus.c
Normal 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
10
src/home/homed-bus.h
Normal 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
877
src/home/homed-home-bus.c
Normal 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
36
src/home/homed-home-bus.h
Normal 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
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
168
src/home/homed-home.h
Normal 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);
|
690
src/home/homed-manager-bus.c
Normal file
690
src/home/homed-manager-bus.c
Normal 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;
|
||||
}
|
6
src/home/homed-manager-bus.h
Normal file
6
src/home/homed-manager-bus.h
Normal 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
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
67
src/home/homed-manager.h
Normal 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);
|
76
src/home/homed-operation.c
Normal file
76
src/home/homed-operation.c
Normal 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;
|
||||
}
|
||||
}
|
62
src/home/homed-operation.h
Normal file
62
src/home/homed-operation.h
Normal 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
370
src/home/homed-varlink.c
Normal 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
8
src/home/homed-varlink.h
Normal 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
46
src/home/homed.c
Normal 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
215
src/home/homework-cifs.c
Normal 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
11
src/home/homework-cifs.h
Normal 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);
|
242
src/home/homework-directory.c
Normal file
242
src/home/homework-directory.c
Normal 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;
|
||||
}
|
10
src/home/homework-directory.h
Normal file
10
src/home/homework-directory.h
Normal 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
644
src/home/homework-fscrypt.c
Normal 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;
|
||||
}
|
10
src/home/homework-fscrypt.h
Normal file
10
src/home/homework-fscrypt.h
Normal 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
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
38
src/home/homework-luks.h
Normal 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
96
src/home/homework-mount.c
Normal 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;
|
||||
}
|
8
src/home/homework-mount.h
Normal file
8
src/home/homework-mount.h
Normal 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
104
src/home/homework-pkcs11.c
Normal 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;
|
||||
}
|
21
src/home/homework-pkcs11.h
Normal file
21
src/home/homework-pkcs11.h
Normal 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
124
src/home/homework-quota.c
Normal 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);
|
||||
}
|
8
src/home/homework-quota.h
Normal file
8
src/home/homework-quota.h
Normal 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
1482
src/home/homework.c
Normal file
File diff suppressed because it is too large
Load Diff
57
src/home/homework.h
Normal file
57
src/home/homework.h
Normal 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
62
src/home/meson.build
Normal 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
|
193
src/home/org.freedesktop.home1.conf
Normal file
193
src/home/org.freedesktop.home1.conf
Normal 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>
|
72
src/home/org.freedesktop.home1.policy
Normal file
72
src/home/org.freedesktop.home1.policy
Normal 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>
|
7
src/home/org.freedesktop.home1.service
Normal file
7
src/home/org.freedesktop.home1.service
Normal 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
140
src/home/pwquality-util.c
Normal 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
|
7
src/home/pwquality-util.h
Normal file
7
src/home/pwquality-util.h
Normal 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
174
src/home/user-record-sign.c
Normal 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;
|
||||
}
|
19
src/home/user-record-sign.h
Normal file
19
src/home/user-record-sign.h
Normal 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
1225
src/home/user-record-util.c
Normal file
File diff suppressed because it is too large
Load Diff
58
src/home/user-record-util.h
Normal file
58
src/home/user-record-util.h
Normal 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);
|
@ -105,5 +105,35 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_SPEED_METER_INACTIVE, EOPNOTSUPP),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_UNMANAGED_INTERFACE, EOPNOTSUPP),
|
||||
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_HOME, EEXIST),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_UID_IN_USE, EEXIST),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_USER_NAME_EXISTS, EEXIST),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_EXISTS, EEXIST),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ALREADY_ACTIVE, EALREADY),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ALREADY_FIXATED, EALREADY),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_UNFIXATED, EADDRNOTAVAIL),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_ACTIVE, EALREADY),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ABSENT, EREMOTE),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_BUSY, EBUSY),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PASSWORD, ENOKEY),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_LOW_PASSWORD_QUALITY, EUCLEAN),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN, EBADSLT),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_NEEDED, ENOANO),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, ERFKILL),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_LOCKED, EOWNERDEAD),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN, ENOLCK),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT, ETOOMANYREFS),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT, EUCLEAN),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_BAD_SIGNATURE, EKEYREJECTED),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_MISMATCH, EUCLEAN),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_DOWNGRADE, ESTALE),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_SIGNED, EROFS),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_BAD_HOME_SIZE, ERANGE),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRIVATE_KEY, ENOPKG),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_LOCKED, ENOEXEC),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_LOCKED, ENOEXEC),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_TOO_MANY_OPERATIONS, ENOBUFS),
|
||||
SD_BUS_ERROR_MAP(BUS_ERROR_AUTHENTICATION_LIMIT_HIT, ETOOMANYREFS),
|
||||
|
||||
SD_BUS_ERROR_MAP_END
|
||||
};
|
||||
|
@ -84,4 +84,35 @@
|
||||
#define BUS_ERROR_SPEED_METER_INACTIVE "org.freedesktop.network1.SpeedMeterInactive"
|
||||
#define BUS_ERROR_UNMANAGED_INTERFACE "org.freedesktop.network1.UnmanagedInterface"
|
||||
|
||||
#define BUS_ERROR_NO_SUCH_HOME "org.freedesktop.home1.NoSuchHome"
|
||||
#define BUS_ERROR_UID_IN_USE "org.freedesktop.home1.UIDInUse"
|
||||
#define BUS_ERROR_USER_NAME_EXISTS "org.freedesktop.home1.UserNameExists"
|
||||
#define BUS_ERROR_HOME_EXISTS "org.freedesktop.home1.HomeExists"
|
||||
#define BUS_ERROR_HOME_ALREADY_ACTIVE "org.freedesktop.home1.HomeAlreadyActive"
|
||||
#define BUS_ERROR_HOME_ALREADY_FIXATED "org.freedesktop.home1.HomeAlreadyFixated"
|
||||
#define BUS_ERROR_HOME_UNFIXATED "org.freedesktop.home1.HomeUnfixated"
|
||||
#define BUS_ERROR_HOME_NOT_ACTIVE "org.freedesktop.home1.HomeNotActive"
|
||||
#define BUS_ERROR_HOME_ABSENT "org.freedesktop.home1.HomeAbsent"
|
||||
#define BUS_ERROR_HOME_BUSY "org.freedesktop.home1.HomeBusy"
|
||||
#define BUS_ERROR_BAD_PASSWORD "org.freedesktop.home1.BadPassword"
|
||||
#define BUS_ERROR_LOW_PASSWORD_QUALITY "org.freedesktop.home1.LowPasswordQuality"
|
||||
#define BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN "org.freedesktop.home1.BadPasswordAndNoToken"
|
||||
#define BUS_ERROR_TOKEN_PIN_NEEDED "org.freedesktop.home1.TokenPinNeeded"
|
||||
#define BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED "org.freedesktop.home1.TokenProtectedAuthenticationPathNeeded"
|
||||
#define BUS_ERROR_TOKEN_PIN_LOCKED "org.freedesktop.home1.TokenPinLocked"
|
||||
#define BUS_ERROR_TOKEN_BAD_PIN "org.freedesktop.home1.BadPin"
|
||||
#define BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT "org.freedesktop.home1.BadPinFewTriesLeft"
|
||||
#define BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT "org.freedesktop.home1.BadPinOneTryLeft"
|
||||
#define BUS_ERROR_BAD_SIGNATURE "org.freedesktop.home1.BadSignature"
|
||||
#define BUS_ERROR_HOME_RECORD_MISMATCH "org.freedesktop.home1.RecordMismatch"
|
||||
#define BUS_ERROR_HOME_RECORD_DOWNGRADE "org.freedesktop.home1.RecordDowngrade"
|
||||
#define BUS_ERROR_HOME_RECORD_SIGNED "org.freedesktop.home1.RecordSigned"
|
||||
#define BUS_ERROR_BAD_HOME_SIZE "org.freedesktop.home1.BadHomeSize"
|
||||
#define BUS_ERROR_NO_PRIVATE_KEY "org.freedesktop.home1.NoPrivateKey"
|
||||
#define BUS_ERROR_HOME_LOCKED "org.freedesktop.home1.HomeLocked"
|
||||
#define BUS_ERROR_HOME_NOT_LOCKED "org.freedesktop.home1.HomeNotLocked"
|
||||
#define BUS_ERROR_NO_DISK_SPACE "org.freedesktop.home1.NoDiskSpace"
|
||||
#define BUS_ERROR_TOO_MANY_OPERATIONS "org.freedesktop.home1.TooManyOperations"
|
||||
#define BUS_ERROR_AUTHENTICATION_LIMIT_HIT "org.freedesktop.home1.AuthenticationLimitHit"
|
||||
|
||||
BUS_ERROR_MAP_ELF_USE(bus_common_errors);
|
||||
|
@ -23,6 +23,7 @@
|
||||
#define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8)
|
||||
#define GPT_VAR SD_ID128_MAKE(4d,21,b0,16,b5,34,45,c2,a9,fb,5c,16,e0,91,fd,2d)
|
||||
#define GPT_TMP SD_ID128_MAKE(7e,c6,f5,57,3b,c5,4a,ca,b2,93,16,ef,5d,f6,39,d1)
|
||||
#define GPT_USER_HOME SD_ID128_MAKE(77,3f,91,ef,66,d4,49,b5,bd,83,d6,83,bf,40,ad,16)
|
||||
|
||||
/* Verity partitions for the root partitions above (we only define them for the root partitions, because only they are
|
||||
* are commonly read-only and hence suitable for verity). */
|
||||
|
@ -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/'],
|
||||
|
36
units/systemd-homed.service.in
Normal file
36
units/systemd-homed.service.in
Normal 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@
|
Loading…
Reference in New Issue
Block a user