Systemd/src/shared/user-record.c
Lennart Poettering 7a8867abfa user-util: rework how we validate user names
This reworks the user validation infrastructure. There are now two
modes. In regular mode we are strict and test against a strict set of
valid chars. And in "relaxed" mode we just filter out some really
obvious, dangerous stuff. i.e. strict is whitelisting what is OK, but
"relaxed" is blacklisting what is really not OK.

The idea is that we use strict mode whenver we allocate a new user
(i.e. in sysusers.d or homed), while "relaxed" mode is when we process
users registered elsewhere, (i.e. userdb, logind, …)

The requirements on user name validity vary wildly. SSSD thinks its fine
to embedd "@" for example, while the suggested NAME_REGEX field on
Debian does not even allow uppercase chars…

This effectively liberaralizes a lot what we expect from usernames.

The code that warns about questionnable user names is now optional and
only used at places such as unit file parsing, so that it doesn't show
up on every userdb query, but only when processing configuration files
that know better.

Fixes: #15149 #15090
2020-04-08 17:11:20 +02:00

1884 lines
87 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include <sys/mount.h>
#include "cgroup-util.h"
#include "dns-domain.h"
#include "env-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "hostname-util.h"
#include "memory-util.h"
#include "path-util.h"
#include "pkcs11-util.h"
#include "rlimit-util.h"
#include "stat-util.h"
#include "string-table.h"
#include "strv.h"
#include "user-record.h"
#include "user-util.h"
#define DEFAULT_RATELIMIT_BURST 30
#define DEFAULT_RATELIMIT_INTERVAL_USEC (1*USEC_PER_MINUTE)
UserRecord* user_record_new(void) {
UserRecord *h;
h = new(UserRecord, 1);
if (!h)
return NULL;
*h = (UserRecord) {
.n_ref = 1,
.disposition = _USER_DISPOSITION_INVALID,
.last_change_usec = UINT64_MAX,
.last_password_change_usec = UINT64_MAX,
.umask = MODE_INVALID,
.nice_level = INT_MAX,
.not_before_usec = UINT64_MAX,
.not_after_usec = UINT64_MAX,
.locked = -1,
.storage = _USER_STORAGE_INVALID,
.access_mode = MODE_INVALID,
.disk_size = UINT64_MAX,
.disk_size_relative = UINT64_MAX,
.tasks_max = UINT64_MAX,
.memory_high = UINT64_MAX,
.memory_max = UINT64_MAX,
.cpu_weight = UINT64_MAX,
.io_weight = UINT64_MAX,
.uid = UID_INVALID,
.gid = GID_INVALID,
.nodev = true,
.nosuid = true,
.luks_discard = -1,
.luks_volume_key_size = UINT64_MAX,
.luks_pbkdf_time_cost_usec = UINT64_MAX,
.luks_pbkdf_memory_cost = UINT64_MAX,
.luks_pbkdf_parallel_threads = UINT64_MAX,
.disk_usage = UINT64_MAX,
.disk_free = UINT64_MAX,
.disk_ceiling = UINT64_MAX,
.disk_floor = UINT64_MAX,
.signed_locally = -1,
.good_authentication_counter = UINT64_MAX,
.bad_authentication_counter = UINT64_MAX,
.last_good_authentication_usec = UINT64_MAX,
.last_bad_authentication_usec = UINT64_MAX,
.ratelimit_begin_usec = UINT64_MAX,
.ratelimit_count = UINT64_MAX,
.ratelimit_interval_usec = UINT64_MAX,
.ratelimit_burst = UINT64_MAX,
.removable = -1,
.enforce_password_policy = -1,
.auto_login = -1,
.stop_delay_usec = UINT64_MAX,
.kill_processes = -1,
.password_change_min_usec = UINT64_MAX,
.password_change_max_usec = UINT64_MAX,
.password_change_warn_usec = UINT64_MAX,
.password_change_inactive_usec = UINT64_MAX,
.password_change_now = -1,
.pkcs11_protected_authentication_path_permitted = -1,
};
return h;
}
static void pkcs11_encrypted_key_done(Pkcs11EncryptedKey *k) {
if (!k)
return;
free(k->uri);
erase_and_free(k->data);
erase_and_free(k->hashed_password);
}
static UserRecord* user_record_free(UserRecord *h) {
if (!h)
return NULL;
free(h->user_name);
free(h->realm);
free(h->user_name_and_realm_auto);
free(h->real_name);
free(h->email_address);
erase_and_free(h->password_hint);
free(h->location);
free(h->icon_name);
free(h->shell);
strv_free(h->environment);
free(h->time_zone);
free(h->preferred_language);
rlimit_free_all(h->rlimits);
free(h->skeleton_directory);
strv_free_erase(h->hashed_password);
strv_free_erase(h->ssh_authorized_keys);
strv_free_erase(h->password);
strv_free_erase(h->pkcs11_pin);
free(h->cifs_service);
free(h->cifs_user_name);
free(h->cifs_domain);
free(h->image_path);
free(h->image_path_auto);
free(h->home_directory);
free(h->home_directory_auto);
strv_free(h->member_of);
free(h->file_system_type);
free(h->luks_cipher);
free(h->luks_cipher_mode);
free(h->luks_pbkdf_hash_algorithm);
free(h->luks_pbkdf_type);
free(h->state);
free(h->service);
strv_free(h->pkcs11_token_uri);
for (size_t i = 0; i < h->n_pkcs11_encrypted_key; i++)
pkcs11_encrypted_key_done(h->pkcs11_encrypted_key + i);
free(h->pkcs11_encrypted_key);
json_variant_unref(h->json);
return mfree(h);
}
DEFINE_TRIVIAL_REF_UNREF_FUNC(UserRecord, user_record, user_record_free);
int json_dispatch_realm(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
char **s = userdata;
const char *n;
int r;
if (json_variant_is_null(variant)) {
*s = mfree(*s);
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
n = json_variant_string(variant);
r = dns_name_is_valid(n);
if (r < 0)
return json_log(variant, flags, r, "Failed to check if JSON field '%s' is a valid DNS domain.", strna(name));
if (r == 0)
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid DNS domain.", strna(name));
r = free_and_strdup(s, n);
if (r < 0)
return json_log(variant, flags, r, "Failed to allocate string: %m");
return 0;
}
static int json_dispatch_gecos(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
char **s = userdata;
const char *n;
int r;
if (json_variant_is_null(variant)) {
*s = mfree(*s);
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
n = json_variant_string(variant);
if (!valid_gecos(n))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid GECOS compatible real name.", strna(name));
r = free_and_strdup(s, n);
if (r < 0)
return json_log(variant, flags, r, "Failed to allocate string: %m");
return 0;
}
static int json_dispatch_nice(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
int *nl = userdata;
intmax_t m;
if (json_variant_is_null(variant)) {
*nl = INT_MAX;
return 0;
}
if (!json_variant_is_integer(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
m = json_variant_integer(variant);
if (m < PRIO_MIN || m >= PRIO_MAX)
return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' is not a valid nice level.", strna(name));
*nl = m;
return 0;
}
static int json_dispatch_rlimit_value(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
rlim_t *ret = userdata;
if (json_variant_is_null(variant))
*ret = RLIM_INFINITY;
else if (json_variant_is_unsigned(variant)) {
uintmax_t w;
w = json_variant_unsigned(variant);
if (w == RLIM_INFINITY || (uintmax_t) w != json_variant_unsigned(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "Resource limit value '%s' is out of range.", name);
*ret = (rlim_t) w;
} else
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit value '%s' is not an unsigned integer.", name);
return 0;
}
static int json_dispatch_rlimits(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
struct rlimit** limits = userdata;
JsonVariant *value;
const char *key;
int r;
assert_se(limits);
if (json_variant_is_null(variant)) {
rlimit_free_all(limits);
return 0;
}
if (!json_variant_is_object(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
JsonVariant *jcur, *jmax;
struct rlimit rl;
const char *p;
int l;
p = startswith(key, "RLIMIT_");
if (!p)
l = -1;
else
l = rlimit_from_string(p);
if (l < 0)
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' not known.", key);
if (!json_variant_is_object(value))
return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' has invalid value.", key);
if (json_variant_elements(value) != 4)
return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' value is does not have two fields as expected.", key);
jcur = json_variant_by_key(value, "cur");
if (!jcur)
return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' lacks 'cur' field.", key);
r = json_dispatch_rlimit_value("cur", jcur, flags, &rl.rlim_cur);
if (r < 0)
return r;
jmax = json_variant_by_key(value, "max");
if (!jmax)
return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Resource limit '%s' lacks 'max' field.", key);
r = json_dispatch_rlimit_value("max", jmax, flags, &rl.rlim_max);
if (r < 0)
return r;
if (limits[l])
*(limits[l]) = rl;
else {
limits[l] = newdup(struct rlimit, &rl, 1);
if (!limits[l])
return log_oom();
}
}
return 0;
}
static int json_dispatch_filename_or_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
char **s = userdata;
const char *n;
int r;
assert(s);
if (json_variant_is_null(variant)) {
*s = mfree(*s);
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
n = json_variant_string(variant);
if (!filename_is_valid(n) && !path_is_normalized(n))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid file name or normalized path.", strna(name));
r = free_and_strdup(s, n);
if (r < 0)
return json_log(variant, flags, r, "Failed to allocate string: %m");
return 0;
}
static int json_dispatch_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
char **s = userdata;
const char *n;
int r;
if (json_variant_is_null(variant)) {
*s = mfree(*s);
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
n = json_variant_string(variant);
if (!path_is_normalized(n))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a normalized file system path.", strna(name));
if (!path_is_absolute(n))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an absolute file system path.", strna(name));
r = free_and_strdup(s, n);
if (r < 0)
return json_log(variant, flags, r, "Failed to allocate string: %m");
return 0;
}
static int json_dispatch_home_directory(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
char **s = userdata;
const char *n;
int r;
if (json_variant_is_null(variant)) {
*s = mfree(*s);
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
n = json_variant_string(variant);
if (!valid_home(n))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid home directory path.", strna(name));
r = free_and_strdup(s, n);
if (r < 0)
return json_log(variant, flags, r, "Failed to allocate string: %m");
return 0;
}
static int json_dispatch_image_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
char **s = userdata;
const char *n;
int r;
if (json_variant_is_null(variant)) {
*s = mfree(*s);
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
n = json_variant_string(variant);
if (empty_or_root(n) || !path_is_valid(n) || !path_is_absolute(n))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid image path.", strna(name));
r = free_and_strdup(s, n);
if (r < 0)
return json_log(variant, flags, r, "Failed to allocate string: %m");
return 0;
}
static int json_dispatch_umask(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
mode_t *m = userdata;
uintmax_t k;
if (json_variant_is_null(variant)) {
*m = (mode_t) -1;
return 0;
}
if (!json_variant_is_unsigned(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a number.", strna(name));
k = json_variant_unsigned(variant);
if (k > 0777)
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' outside of valid range 0…0777.", strna(name));
*m = (mode_t) k;
return 0;
}
static int json_dispatch_access_mode(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
mode_t *m = userdata;
uintmax_t k;
if (json_variant_is_null(variant)) {
*m = (mode_t) -1;
return 0;
}
if (!json_variant_is_unsigned(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a number.", strna(name));
k = json_variant_unsigned(variant);
if (k > 07777)
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' outside of valid range 0…07777.", strna(name));
*m = (mode_t) k;
return 0;
}
static int json_dispatch_environment(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
_cleanup_strv_free_ char **n = NULL;
char ***l = userdata;
size_t i;
int r;
if (json_variant_is_null(variant)) {
*l = strv_free(*l);
return 0;
}
if (!json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
for (i = 0; i < json_variant_elements(variant); i++) {
_cleanup_free_ char *c = NULL;
JsonVariant *e;
const char *a;
e = json_variant_by_index(variant, i);
if (!json_variant_is_string(e))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
assert_se(a = json_variant_string(e));
if (!env_assignment_is_valid(a))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of environment variables.", strna(name));
c = strdup(a);
if (!c)
return json_log_oom(variant, flags);
r = strv_env_replace(&n, c);
if (r < 0)
return json_log_oom(variant, flags);
c = NULL;
}
strv_free_and_replace(*l, n);
return 0;
}
int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
UserDisposition *disposition = userdata, k;
if (json_variant_is_null(variant)) {
*disposition = _USER_DISPOSITION_INVALID;
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
k = user_disposition_from_string(json_variant_string(variant));
if (k < 0)
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Disposition type '%s' not known.", json_variant_string(variant));
*disposition = k;
return 0;
}
static int json_dispatch_storage(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
UserStorage *storage = userdata, k;
if (json_variant_is_null(variant)) {
*storage = _USER_STORAGE_INVALID;
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
k = user_storage_from_string(json_variant_string(variant));
if (k < 0)
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Storage type '%s' not known.", json_variant_string(variant));
*storage = k;
return 0;
}
static int json_dispatch_disk_size(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
uint64_t *size = userdata;
uintmax_t k;
if (json_variant_is_null(variant)) {
*size = UINT64_MAX;
return 0;
}
if (!json_variant_is_unsigned(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name));
k = json_variant_unsigned(variant);
if (k < USER_DISK_SIZE_MIN || k > USER_DISK_SIZE_MAX)
return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' is not in valid range %" PRIu64 "…%" PRIu64 ".", strna(name), USER_DISK_SIZE_MIN, USER_DISK_SIZE_MAX);
*size = k;
return 0;
}
static int json_dispatch_tasks_or_memory_max(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
uint64_t *limit = userdata;
uintmax_t k;
if (json_variant_is_null(variant)) {
*limit = UINT64_MAX;
return 0;
}
if (!json_variant_is_unsigned(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a integer.", strna(name));
k = json_variant_unsigned(variant);
if (k <= 0 || k >= UINT64_MAX)
return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' is not in valid range %" PRIu64 "…%" PRIu64 ".", strna(name), (uint64_t) 1, UINT64_MAX-1);
*limit = k;
return 0;
}
static int json_dispatch_weight(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
uint64_t *weight = userdata;
uintmax_t k;
if (json_variant_is_null(variant)) {
*weight = UINT64_MAX;
return 0;
}
if (!json_variant_is_unsigned(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a integer.", strna(name));
k = json_variant_unsigned(variant);
if (k <= CGROUP_WEIGHT_MIN || k >= CGROUP_WEIGHT_MAX)
return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' is not in valid range %" PRIu64 "…%" PRIu64 ".", strna(name), (uint64_t) CGROUP_WEIGHT_MIN, (uint64_t) CGROUP_WEIGHT_MAX);
*weight = k;
return 0;
}
int json_dispatch_user_group_list(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
_cleanup_strv_free_ char **l = NULL;
char ***list = userdata;
JsonVariant *e;
int r;
if (!json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
if (!json_variant_is_string(e))
return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
if (!valid_user_group_name(json_variant_string(e), FLAGS_SET(flags, JSON_RELAX) ? VALID_USER_RELAX : 0))
return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a valid user/group name: %s", json_variant_string(e));
r = strv_extend(&l, json_variant_string(e));
if (r < 0)
return json_log(e, flags, r, "Failed to append array element: %m");
}
r = strv_extend_strv(list, l, true);
if (r < 0)
return json_log(variant, flags, r, "Failed to merge user/group arrays: %m");
return 0;
}
static int dispatch_secret(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch secret_dispatch_table[] = {
{ "password", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, password), 0 },
{ "pkcs11Pin", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, pkcs11_pin), 0 },
{ "pkcs11ProtectedAuthenticationPathPermitted", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, pkcs11_protected_authentication_path_permitted), 0 },
{},
};
return json_dispatch(variant, secret_dispatch_table, NULL, flags, userdata);
}
static int dispatch_pkcs11_uri(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
char **s = userdata;
const char *n;
int r;
if (json_variant_is_null(variant)) {
*s = mfree(*s);
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
n = json_variant_string(variant);
if (!pkcs11_uri_valid(n))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid RFC7512 PKCS#11 URI.", strna(name));
r = free_and_strdup(s, n);
if (r < 0)
return json_log(variant, flags, r, "Failed to allocate string: %m");
return 0;
}
static int dispatch_pkcs11_uri_array(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
_cleanup_strv_free_ char **z = NULL;
char ***l = userdata;
JsonVariant *e;
int r;
if (json_variant_is_null(variant)) {
*l = strv_free(*l);
return 0;
}
if (json_variant_is_string(variant)) {
const char *n;
n = json_variant_string(variant);
if (!pkcs11_uri_valid(n))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid RFC7512 PKCS#11 URI.", strna(name));
z = strv_new(n);
if (!z)
return log_oom();
} else {
if (!json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string or array of strings.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
const char *n;
if (!json_variant_is_string(e))
return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
n = json_variant_string(e);
if (!pkcs11_uri_valid(n))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element in '%s' is not a valid RFC7512 PKCS#11 URI: %s", strna(name), n);
r = strv_extend(&z, n);
if (r < 0)
return log_oom();
}
}
strv_free_and_replace(*l, z);
return 0;
}
static int dispatch_pkcs11_key_data(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
Pkcs11EncryptedKey *k = userdata;
size_t l;
void *b;
int r;
if (json_variant_is_null(variant)) {
k->data = mfree(k->data);
k->size = 0;
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
r = unbase64mem(json_variant_string(variant), (size_t) -1, &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode encrypted PKCS#11 key: %m");
erase_and_free(k->data);
k->data = b;
k->size = l;
return 0;
}
static int dispatch_pkcs11_key(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
UserRecord *h = userdata;
JsonVariant *e;
int r;
if (!json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
Pkcs11EncryptedKey *array, *k;
static const JsonDispatch pkcs11_key_dispatch_table[] = {
{ "uri", JSON_VARIANT_STRING, dispatch_pkcs11_uri, offsetof(Pkcs11EncryptedKey, uri), JSON_MANDATORY },
{ "data", JSON_VARIANT_STRING, dispatch_pkcs11_key_data, 0, JSON_MANDATORY },
{ "hashedPassword", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Pkcs11EncryptedKey, hashed_password), JSON_MANDATORY },
{},
};
if (!json_variant_is_object(e))
return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
array = reallocarray(h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key + 1, sizeof(Pkcs11EncryptedKey));
if (!array)
return log_oom();
h->pkcs11_encrypted_key = array;
k = h->pkcs11_encrypted_key + h->n_pkcs11_encrypted_key;
*k = (Pkcs11EncryptedKey) {};
r = json_dispatch(e, pkcs11_key_dispatch_table, NULL, flags, k);
if (r < 0) {
pkcs11_encrypted_key_done(k);
return r;
}
h->n_pkcs11_encrypted_key++;
}
return 0;
}
static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch privileged_dispatch_table[] = {
{ "passwordHint", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, password_hint), 0 },
{ "hashedPassword", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, hashed_password), JSON_SAFE },
{ "sshAuthorizedKeys", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, ssh_authorized_keys), 0 },
{ "pkcs11EncryptedKey", JSON_VARIANT_ARRAY, dispatch_pkcs11_key, 0, 0 },
{},
};
return json_dispatch(variant, privileged_dispatch_table, NULL, flags, userdata);
}
static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch binding_dispatch_table[] = {
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_image_path, offsetof(UserRecord, image_path), 0 },
{ "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
{},
};
char smid[SD_ID128_STRING_MAX];
JsonVariant *m;
sd_id128_t mid;
int r;
if (!variant)
return 0;
if (!json_variant_is_object(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
r = sd_id128_get_machine(&mid);
if (r < 0)
return json_log(variant, flags, r, "Failed to determine machine ID: %m");
m = json_variant_by_key(variant, sd_id128_to_string(mid, smid));
if (!m)
return 0;
return json_dispatch(m, binding_dispatch_table, NULL, flags, userdata);
}
int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags) {
sd_id128_t mid;
int r;
r = sd_id128_get_machine(&mid);
if (r < 0)
return json_log(ids, flags, r, "Failed to acquire machine ID: %m");
if (json_variant_is_string(ids)) {
sd_id128_t k;
r = sd_id128_from_string(json_variant_string(ids), &k);
if (r < 0) {
json_log(ids, flags, r, "%s is not a valid machine ID, ignoring: %m", json_variant_string(ids));
return 0;
}
return sd_id128_equal(mid, k);
}
if (json_variant_is_array(ids)) {
JsonVariant *e;
JSON_VARIANT_ARRAY_FOREACH(e, ids) {
sd_id128_t k;
if (!json_variant_is_string(e)) {
json_log(e, flags, 0, "Machine ID is not a string, ignoring: %m");
continue;
}
r = sd_id128_from_string(json_variant_string(e), &k);
if (r < 0) {
json_log(e, flags, r, "%s is not a valid machine ID, ignoring: %m", json_variant_string(e));
continue;
}
if (sd_id128_equal(mid, k))
return true;
}
return false;
}
json_log(ids, flags, 0, "Machine ID is not a string or array of strings, ignoring: %m");
return false;
}
int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags) {
_cleanup_free_ char *hn = NULL;
int r;
r = gethostname_strict(&hn);
if (r == -ENXIO) {
json_log(hns, flags, r, "No hostname set, not matching perMachine hostname record: %m");
return false;
}
if (r < 0)
return json_log(hns, flags, r, "Failed to acquire hostname: %m");
if (json_variant_is_string(hns))
return streq(json_variant_string(hns), hn);
if (json_variant_is_array(hns)) {
JsonVariant *e;
JSON_VARIANT_ARRAY_FOREACH(e, hns) {
if (!json_variant_is_string(e)) {
json_log(e, flags, 0, "Hostname is not a string, ignoring: %m");
continue;
}
if (streq(json_variant_string(hns), hn))
return true;
}
return false;
}
json_log(hns, flags, 0, "Hostname is not a string or array of strings, ignoring: %m");
return false;
}
static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch per_machine_dispatch_table[] = {
{ "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
{ "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
{ "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
{ "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
{ "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
{ "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
{ "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
{ "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
{ "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0, },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
{ "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
{ "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
{ "stopDelayUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
{ "passwordChangeMaxUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
{ "passwordChangeWarnUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
{ "passwordChangeInactiveUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
{ "passwordChangeNow", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
{ "pkcs11TokenUri", JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
{},
};
JsonVariant *e;
int r;
if (!variant)
return 0;
if (!json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
bool matching = false;
JsonVariant *m;
if (!json_variant_is_object(e))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
m = json_variant_by_key(e, "matchMachineId");
if (m) {
r = per_machine_id_match(m, flags);
if (r < 0)
return r;
matching = r > 0;
}
if (!matching) {
m = json_variant_by_key(e, "matchHostname");
if (m) {
r = per_machine_hostname_match(m, flags);
if (r < 0)
return r;
matching = r > 0;
}
}
if (!matching)
continue;
r = json_dispatch(e, per_machine_dispatch_table, NULL, flags, userdata);
if (r < 0)
return r;
}
return 0;
}
static int dispatch_status(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch status_dispatch_table[] = {
{ "diskUsage", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 },
{ "diskFree", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 },
{ "diskCeiling", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 },
{ "diskFloor", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 },
{ "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
{ "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 },
{ "goodAuthenticationCounter", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 },
{ "badAuthenticationCounter", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 },
{ "lastGoodAuthenticationUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 },
{ "lastBadAuthenticationUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 },
{ "rateLimitBeginUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 },
{ "rateLimitCount", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 },
{ "removable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, removable), 0 },
{},
};
char smid[SD_ID128_STRING_MAX];
JsonVariant *m;
sd_id128_t mid;
int r;
if (!variant)
return 0;
if (!json_variant_is_object(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
r = sd_id128_get_machine(&mid);
if (r < 0)
return json_log(variant, flags, r, "Failed to determine machine ID: %m");
m = json_variant_by_key(variant, sd_id128_to_string(mid, smid));
if (!m)
return 0;
return json_dispatch(m, status_dispatch_table, NULL, flags, userdata);
}
static int user_record_augment(UserRecord *h, JsonDispatchFlags json_flags) {
assert(h);
if (!FLAGS_SET(h->mask, USER_RECORD_REGULAR))
return 0;
assert(h->user_name);
if (!h->user_name_and_realm_auto && h->realm) {
h->user_name_and_realm_auto = strjoin(h->user_name, "@", h->realm);
if (!h->user_name_and_realm_auto)
return json_log_oom(h->json, json_flags);
}
/* Let's add in the following automatisms only for regular users, they dont make sense for any others */
if (user_record_disposition(h) != USER_REGULAR)
return 0;
if (!h->home_directory && !h->home_directory_auto) {
h->home_directory_auto = path_join("/home/", h->user_name);
if (!h->home_directory_auto)
return json_log_oom(h->json, json_flags);
}
if (!h->image_path && !h->image_path_auto) {
const char *suffix;
UserStorage storage;
storage = user_record_storage(h);
if (storage == USER_LUKS)
suffix = ".home";
else if (IN_SET(storage, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT))
suffix = ".homedir";
else
suffix = NULL;
if (suffix) {
h->image_path_auto = strjoin("/home/", user_record_user_name_and_realm(h), suffix);
if (!h->image_path_auto)
return json_log_oom(h->json, json_flags);
}
}
return 0;
}
int user_group_record_mangle(
JsonVariant *v,
UserRecordLoadFlags load_flags,
JsonVariant **ret_variant,
UserRecordMask *ret_mask) {
static const struct {
UserRecordMask mask;
const char *name;
} mask_field[] = {
{ USER_RECORD_PRIVILEGED, "privileged" },
{ USER_RECORD_SECRET, "secret" },
{ USER_RECORD_BINDING, "binding" },
{ USER_RECORD_PER_MACHINE, "perMachine" },
{ USER_RECORD_STATUS, "status" },
{ USER_RECORD_SIGNATURE, "signature" },
};
JsonDispatchFlags json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
JsonVariant *array[ELEMENTSOF(mask_field) * 2];
size_t n_retain = 0, i;
UserRecordMask m = 0;
int r;
assert((load_flags & _USER_RECORD_MASK_MAX) == 0); /* detect mistakes when accidentally passing
* UserRecordMask bit masks as UserRecordLoadFlags
* value */
assert(v);
assert(ret_variant);
assert(ret_mask);
/* Note that this function is shared with the group record parser, hence we try to be generic in our
* log message wording here, to cover both cases. */
if (!json_variant_is_object(v))
return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is not a JSON object, refusing.");
if (USER_RECORD_ALLOW_MASK(load_flags) == 0) /* allow nothing? */
return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Nothing allowed in record, refusing.");
if (USER_RECORD_STRIP_MASK(load_flags) == _USER_RECORD_MASK_MAX) /* strip everything? */
return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Stripping everything from record, refusing.");
/* Check if we have the special sections and if they match our flags set */
for (i = 0; i < ELEMENTSOF(mask_field); i++) {
JsonVariant *e, *k;
if (FLAGS_SET(USER_RECORD_STRIP_MASK(load_flags), mask_field[i].mask)) {
if (!w)
w = json_variant_ref(v);
r = json_variant_filter(&w, STRV_MAKE(mask_field[i].name));
if (r < 0)
return json_log(w, json_flags, r, "Failed to remove field from variant: %m");
continue;
}
e = json_variant_by_key_full(v, mask_field[i].name, &k);
if (e) {
if (!FLAGS_SET(USER_RECORD_ALLOW_MASK(load_flags), mask_field[i].mask))
return json_log(e, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", mask_field[i].name);
if (FLAGS_SET(load_flags, USER_RECORD_STRIP_REGULAR)) {
array[n_retain++] = k;
array[n_retain++] = e;
}
m |= mask_field[i].mask;
} else {
if (FLAGS_SET(USER_RECORD_REQUIRE_MASK(load_flags), mask_field[i].mask))
return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks '%s' field, which is required.", mask_field[i].name);
}
}
if (FLAGS_SET(load_flags, USER_RECORD_STRIP_REGULAR)) {
/* If we are supposed to strip regular items, then let's instead just allocate a new object
* with just the stuff we need. */
w = json_variant_unref(w);
r = json_variant_new_object(&w, array, n_retain);
if (r < 0)
return json_log(v, json_flags, r, "Failed to allocate new object: %m");
} else {
/* And now check if there's anything else in the record */
for (i = 0; i < json_variant_elements(v); i += 2) {
const char *f;
bool special = false;
size_t j;
assert_se(f = json_variant_string(json_variant_by_index(v, i)));
for (j = 0; j < ELEMENTSOF(mask_field); j++)
if (streq(f, mask_field[j].name)) { /* already covered in the loop above */
special = true;
continue;
}
if (!special) {
if ((load_flags & (USER_RECORD_ALLOW_REGULAR|USER_RECORD_REQUIRE_REGULAR)) == 0)
return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record contains '%s' field, which is not allowed.", f);
m |= USER_RECORD_REGULAR;
break;
}
}
}
if (FLAGS_SET(load_flags, USER_RECORD_REQUIRE_REGULAR) && !FLAGS_SET(m, USER_RECORD_REGULAR))
return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record lacks basic identity fields, which are required.");
if (m == 0)
return json_log(v, json_flags, SYNTHETIC_ERRNO(EBADMSG), "Record is empty.");
if (w)
*ret_variant = TAKE_PTR(w);
else
*ret_variant = json_variant_ref(v);
*ret_mask = m;
return 0;
}
int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_flags) {
static const JsonDispatch user_dispatch_table[] = {
{ "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), JSON_RELAX},
{ "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 },
{ "realName", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(UserRecord, real_name), 0 },
{ "emailAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, email_address), JSON_SAFE },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
{ "disposition", JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(UserRecord, disposition), 0 },
{ "lastChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_change_usec), 0 },
{ "lastPasswordChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_password_change_usec), 0 },
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
{ "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
{ "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
{ "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
{ "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
{ "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
{ "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
{ "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0 },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
{ "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
{ "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
{ "stopDelayUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
{ "passwordChangeMaxUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
{ "passwordChangeWarnUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
{ "passwordChangeInactiveUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
{ "passwordChangeNow", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
{ "pkcs11TokenUri", JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
{ "secret", JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 },
{ "privileged", JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
/* Ignore the perMachine, binding, status stuff here, and process it later, so that it overrides whatever is set above */
{ "perMachine", JSON_VARIANT_ARRAY, NULL, 0, 0 },
{ "binding", JSON_VARIANT_OBJECT, NULL, 0, 0 },
{ "status", JSON_VARIANT_OBJECT, NULL, 0, 0 },
/* Ignore 'signature', we check it with explicit accessors instead */
{ "signature", JSON_VARIANT_ARRAY, NULL, 0, 0 },
{},
};
JsonDispatchFlags json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
int r;
assert(h);
assert(!h->json);
/* Note that this call will leave a half-initialized record around on failure! */
r = user_group_record_mangle(v, load_flags, &h->json, &h->mask);
if (r < 0)
return r;
r = json_dispatch(h->json, user_dispatch_table, NULL, json_flags, h);
if (r < 0)
return r;
/* During the parsing operation above we ignored the 'perMachine', 'binding' and 'status' fields,
* since we want them to override the global options. Let's process them now. */
r = dispatch_per_machine("perMachine", json_variant_by_key(h->json, "perMachine"), json_flags, h);
if (r < 0)
return r;
r = dispatch_binding("binding", json_variant_by_key(h->json, "binding"), json_flags, h);
if (r < 0)
return r;
r = dispatch_status("status", json_variant_by_key(h->json, "status"), json_flags, h);
if (r < 0)
return r;
if (FLAGS_SET(h->mask, USER_RECORD_REGULAR) && !h->user_name)
return json_log(h->json, json_flags, SYNTHETIC_ERRNO(EINVAL), "User name field missing, refusing.");
r = user_record_augment(h, json_flags);
if (r < 0)
return r;
return 0;
}
int user_record_build(UserRecord **ret, ...) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(user_record_unrefp) UserRecord *u = NULL;
va_list ap;
int r;
assert(ret);
va_start(ap, ret);
r = json_buildv(&v, ap);
va_end(ap);
if (r < 0)
return r;
u = user_record_new();
if (!u)
return -ENOMEM;
r = user_record_load(u, v, USER_RECORD_LOAD_FULL);
if (r < 0)
return r;
*ret = TAKE_PTR(u);
return 0;
}
const char *user_record_user_name_and_realm(UserRecord *h) {
assert(h);
/* Return the pre-initialized joined string if it is defined */
if (h->user_name_and_realm_auto)
return h->user_name_and_realm_auto;
/* If it's not defined then we cannot have a realm */
assert(!h->realm);
return h->user_name;
}
UserStorage user_record_storage(UserRecord *h) {
assert(h);
if (h->storage >= 0)
return h->storage;
return USER_CLASSIC;
}
const char *user_record_file_system_type(UserRecord *h) {
assert(h);
return h->file_system_type ?: "ext4";
}
const char *user_record_skeleton_directory(UserRecord *h) {
assert(h);
return h->skeleton_directory ?: "/etc/skel";
}
mode_t user_record_access_mode(UserRecord *h) {
assert(h);
return h->access_mode != (mode_t) -1 ? h->access_mode : 0700;
}
const char* user_record_home_directory(UserRecord *h) {
assert(h);
if (h->home_directory)
return h->home_directory;
if (h->home_directory_auto)
return h->home_directory_auto;
/* The root user is special, hence be special about it */
if (streq_ptr(h->user_name, "root"))
return "/root";
return "/";
}
const char *user_record_image_path(UserRecord *h) {
assert(h);
if (h->image_path)
return h->image_path;
if (h->image_path_auto)
return h->image_path_auto;
return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ? user_record_home_directory(h) : NULL;
}
const char *user_record_cifs_user_name(UserRecord *h) {
assert(h);
return h->cifs_user_name ?: h->user_name;
}
unsigned long user_record_mount_flags(UserRecord *h) {
assert(h);
return (h->nosuid ? MS_NOSUID : 0) |
(h->noexec ? MS_NOEXEC : 0) |
(h->nodev ? MS_NODEV : 0);
}
const char *user_record_shell(UserRecord *h) {
assert(h);
if (h->shell)
return h->shell;
if (streq_ptr(h->user_name, "root"))
return "/bin/sh";
if (user_record_disposition(h) == USER_REGULAR)
return "/bin/bash";
return NOLOGIN;
}
const char *user_record_real_name(UserRecord *h) {
assert(h);
return h->real_name ?: h->user_name;
}
bool user_record_luks_discard(UserRecord *h) {
const char *ip;
assert(h);
if (h->luks_discard >= 0)
return h->luks_discard;
ip = user_record_image_path(h);
if (!ip)
return false;
/* Use discard by default if we are referring to a real block device, but not when operating on a
* loopback device. We want to optimize for SSD and flash storage after all, but we should be careful
* when storing stuff on top of regular file systems in loopback files as doing discard then would
* mean thin provisioning and we should not do that willy-nilly since it means we'll risk EIO later
* on should the disk space to back our file systems not be available. */
return path_startswith(ip, "/dev/");
}
const char *user_record_luks_cipher(UserRecord *h) {
assert(h);
return h->luks_cipher ?: "aes";
}
const char *user_record_luks_cipher_mode(UserRecord *h) {
assert(h);
return h->luks_cipher_mode ?: "xts-plain64";
}
uint64_t user_record_luks_volume_key_size(UserRecord *h) {
assert(h);
/* We return a value here that can be cast without loss into size_t which is what libcrypsetup expects */
if (h->luks_volume_key_size == UINT64_MAX)
return 256 / 8;
return MIN(h->luks_volume_key_size, SIZE_MAX);
}
const char* user_record_luks_pbkdf_type(UserRecord *h) {
assert(h);
return h->luks_pbkdf_type ?: "argon2i";
}
uint64_t user_record_luks_pbkdf_time_cost_usec(UserRecord *h) {
assert(h);
/* Returns a value with ms granularity, since that's what libcryptsetup expects */
if (h->luks_pbkdf_time_cost_usec == UINT64_MAX)
return 500 * USEC_PER_MSEC; /* We default to 500ms, in contrast to libcryptsetup's 2s, which is just awfully slow on every login */
return MIN(DIV_ROUND_UP(h->luks_pbkdf_time_cost_usec, USEC_PER_MSEC), UINT32_MAX) * USEC_PER_MSEC;
}
uint64_t user_record_luks_pbkdf_memory_cost(UserRecord *h) {
assert(h);
/* Returns a value with kb granularity, since that's what libcryptsetup expects */
if (h->luks_pbkdf_memory_cost == UINT64_MAX)
return 64*1024*1024; /* We default to 64M, since this should work on smaller systems too */
return MIN(DIV_ROUND_UP(h->luks_pbkdf_memory_cost, 1024), UINT32_MAX) * 1024;
}
uint64_t user_record_luks_pbkdf_parallel_threads(UserRecord *h) {
assert(h);
if (h->luks_pbkdf_memory_cost == UINT64_MAX)
return 1; /* We default to 1, since this should work on smaller systems too */
return MIN(h->luks_pbkdf_parallel_threads, UINT32_MAX);
}
const char *user_record_luks_pbkdf_hash_algorithm(UserRecord *h) {
assert(h);
return h->luks_pbkdf_hash_algorithm ?: "sha512";
}
gid_t user_record_gid(UserRecord *h) {
assert(h);
if (gid_is_valid(h->gid))
return h->gid;
return (gid_t) h->uid;
}
UserDisposition user_record_disposition(UserRecord *h) {
assert(h);
if (h->disposition >= 0)
return h->disposition;
/* If not declared, derive from UID */
if (!uid_is_valid(h->uid))
return _USER_DISPOSITION_INVALID;
if (h->uid == 0 || h->uid == UID_NOBODY)
return USER_INTRINSIC;
if (uid_is_system(h->uid))
return USER_SYSTEM;
if (uid_is_dynamic(h->uid))
return USER_DYNAMIC;
if (uid_is_container(h->uid))
return USER_CONTAINER;
if (h->uid > INT32_MAX)
return USER_RESERVED;
return USER_REGULAR;
}
int user_record_removable(UserRecord *h) {
UserStorage storage;
assert(h);
if (h->removable >= 0)
return h->removable;
/* Refuse to decide for classic records */
storage = user_record_storage(h);
if (h->storage < 0 || h->storage == USER_CLASSIC)
return -1;
/* For now consider only LUKS home directories with a reference by path as removable */
return storage == USER_LUKS && path_startswith(user_record_image_path(h), "/dev/");
}
uint64_t user_record_ratelimit_interval_usec(UserRecord *h) {
assert(h);
if (h->ratelimit_interval_usec == UINT64_MAX)
return DEFAULT_RATELIMIT_INTERVAL_USEC;
return h->ratelimit_interval_usec;
}
uint64_t user_record_ratelimit_burst(UserRecord *h) {
assert(h);
if (h->ratelimit_burst == UINT64_MAX)
return DEFAULT_RATELIMIT_BURST;
return h->ratelimit_burst;
}
bool user_record_can_authenticate(UserRecord *h) {
assert(h);
/* Returns true if there's some form of property configured that the user can authenticate against */
if (h->n_pkcs11_encrypted_key > 0)
return true;
return !strv_isempty(h->hashed_password);
}
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
assert(h);
/* Calculates when the it's possible to login next. Returns:
*
* UINT64_MAX → Nothing known
* 0 → Right away
* Any other → Next time in CLOCK_REALTIME in usec (which could be in the past)
*/
if (h->ratelimit_begin_usec == UINT64_MAX ||
h->ratelimit_count == UINT64_MAX)
return UINT64_MAX;
if (h->ratelimit_count < user_record_ratelimit_burst(h))
return 0;
return usec_add(h->ratelimit_begin_usec, user_record_ratelimit_interval_usec(h));
}
bool user_record_equal(UserRecord *a, UserRecord *b) {
assert(a);
assert(b);
/* We assume that when a record is modified its JSON data is updated at the same time, hence it's
* sufficient to compare the JSON data. */
return json_variant_equal(a->json, b->json);
}
bool user_record_compatible(UserRecord *a, UserRecord *b) {
assert(a);
assert(b);
/* If either lacks a the regular section, we can't really decide, let's hence say they are
* incompatible. */
if (!(a->mask & b->mask & USER_RECORD_REGULAR))
return false;
return streq_ptr(a->user_name, b->user_name) &&
streq_ptr(a->realm, b->realm);
}
int user_record_compare_last_change(UserRecord *a, UserRecord *b) {
assert(a);
assert(b);
if (a->last_change_usec == b->last_change_usec)
return 0;
/* Always consider a record with a timestamp newer than one without */
if (a->last_change_usec == UINT64_MAX)
return -1;
if (b->last_change_usec == UINT64_MAX)
return 1;
return CMP(a->last_change_usec, b->last_change_usec);
}
int user_record_clone(UserRecord *h, UserRecordLoadFlags flags, UserRecord **ret) {
_cleanup_(user_record_unrefp) UserRecord *c = NULL;
int r;
assert(h);
assert(ret);
c = user_record_new();
if (!c)
return -ENOMEM;
r = user_record_load(c, h->json, flags);
if (r < 0)
return r;
*ret = TAKE_PTR(c);
return 0;
}
int user_record_masked_equal(UserRecord *a, UserRecord *b, UserRecordMask mask) {
_cleanup_(user_record_unrefp) UserRecord *x = NULL, *y = NULL;
int r;
assert(a);
assert(b);
/* Compares the two records, but ignores anything not listed in the specified mask */
if ((a->mask & ~mask) != 0) {
r = user_record_clone(a, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX), &x);
if (r < 0)
return r;
a = x;
}
if ((b->mask & ~mask) != 0) {
r = user_record_clone(b, USER_RECORD_ALLOW(mask) | USER_RECORD_STRIP(~mask & _USER_RECORD_MASK_MAX), &y);
if (r < 0)
return r;
b = y;
}
return user_record_equal(a, b);
}
int user_record_test_blocked(UserRecord *h) {
usec_t n;
/* Checks whether access to the specified user shall be allowed at the moment. Returns:
*
* -ESTALE: Record is from the future
* -ENOLCK: Record is blocked
* -EL2HLT: Record is not valid yet
* -EL3HLT: Record is not valid anymore
*
*/
assert(h);
n = now(CLOCK_REALTIME);
if (h->last_change_usec != UINT64_MAX &&
h->last_change_usec > n) /* Don't allow log ins when the record is from the future */
return -ESTALE;
if (h->locked > 0)
return -ENOLCK;
if (h->not_before_usec != UINT64_MAX && n < h->not_before_usec)
return -EL2HLT;
if (h->not_after_usec != UINT64_MAX && n > h->not_after_usec)
return -EL3HLT;
return 0;
}
int user_record_test_password_change_required(UserRecord *h) {
bool change_permitted;
usec_t n;
assert(h);
/* Checks whether the user must change the password when logging in
-EKEYREVOKED: Change password now because admin said so
-EOWNERDEAD: Change password now because it expired
-EKEYREJECTED: Password is expired, no changing is allowed
-EKEYEXPIRED: Password is about to expire, warn user
-ENETDOWN: Record has expiration info but no password change timestamp
-EROFS: No password change required nor permitted
0: No password change required, but permitted
*/
/* If a pasword change request has been set explicitly, it overrides everything */
if (h->password_change_now > 0)
return -EKEYREVOKED;
n = now(CLOCK_REALTIME);
/* Then, let's check if password changing is currently allowed at all */
if (h->password_change_min_usec != UINT64_MAX) {
/* Expiry configured but no password change timestamp known? */
if (h->last_password_change_usec == UINT64_MAX)
return -ENETDOWN;
if (h->password_change_min_usec >= UINT64_MAX - h->last_password_change_usec)
change_permitted = false;
else
change_permitted = n >= h->last_password_change_usec + h->password_change_min_usec;
} else
change_permitted = true;
/* Let's check whether the password has expired. */
if (!(h->password_change_max_usec == UINT64_MAX ||
h->password_change_max_usec >= UINT64_MAX - h->last_password_change_usec)) {
uint64_t change_before;
/* Expiry configured but no password change timestamp known? */
if (h->last_password_change_usec == UINT64_MAX)
return -ENETDOWN;
/* Password is in inactive phase? */
if (h->password_change_inactive_usec != UINT64_MAX &&
h->password_change_inactive_usec < UINT64_MAX - h->password_change_max_usec) {
usec_t added;
added = h->password_change_inactive_usec + h->password_change_max_usec;
if (added < UINT64_MAX - h->last_password_change_usec &&
n >= h->last_password_change_usec + added)
return -EKEYREJECTED;
}
/* Password needs to be changed now? */
change_before = h->last_password_change_usec + h->password_change_max_usec;
if (n >= change_before)
return change_permitted ? -EOWNERDEAD : -EKEYREJECTED;
/* Warn user? */
if (h->password_change_warn_usec != UINT64_MAX &&
(change_before < h->password_change_warn_usec ||
n >= change_before - h->password_change_warn_usec))
return change_permitted ? -EKEYEXPIRED : -EROFS;
}
/* No password changing necessary */
return change_permitted ? 0 : -EROFS;
}
static const char* const user_storage_table[_USER_STORAGE_MAX] = {
[USER_CLASSIC] = "classic",
[USER_LUKS] = "luks",
[USER_DIRECTORY] = "directory",
[USER_SUBVOLUME] = "subvolume",
[USER_FSCRYPT] = "fscrypt",
[USER_CIFS] = "cifs",
};
DEFINE_STRING_TABLE_LOOKUP(user_storage, UserStorage);
static const char* const user_disposition_table[_USER_DISPOSITION_MAX] = {
[USER_INTRINSIC] = "intrinsic",
[USER_SYSTEM] = "system",
[USER_DYNAMIC] = "dynamic",
[USER_REGULAR] = "regular",
[USER_CONTAINER] = "container",
[USER_RESERVED] = "reserved",
};
DEFINE_STRING_TABLE_LOOKUP(user_disposition, UserDisposition);