Files
systemd/src/shared/group-record.c
Lennart Poettering f0e4244b2f tree-wide: use JSON_ALLOW_EXTENSIONS when disptching at various places
If we want to allow method replies to be extended without this breaking
compat, then we should set this flag. Do so at various method call
replies hence.

Also do it when parsing user/group records, which are expressly
documented to be extensible, as well as the hibernate JSON record.
2024-01-05 12:43:06 +01:00

348 lines
13 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "group-record.h"
#include "strv.h"
#include "uid-alloc-range.h"
#include "user-util.h"
GroupRecord* group_record_new(void) {
GroupRecord *h;
h = new(GroupRecord, 1);
if (!h)
return NULL;
*h = (GroupRecord) {
.n_ref = 1,
.disposition = _USER_DISPOSITION_INVALID,
.last_change_usec = UINT64_MAX,
.gid = GID_INVALID,
};
return h;
}
static GroupRecord *group_record_free(GroupRecord *g) {
if (!g)
return NULL;
free(g->group_name);
free(g->realm);
free(g->group_name_and_realm_auto);
free(g->description);
strv_free(g->members);
free(g->service);
strv_free(g->administrators);
strv_free_erase(g->hashed_password);
json_variant_unref(g->json);
return mfree(g);
}
DEFINE_TRIVIAL_REF_UNREF_FUNC(GroupRecord, group_record, group_record_free);
static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch privileged_dispatch_table[] = {
{ "hashedPassword", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(GroupRecord, hashed_password), JSON_SAFE },
{},
};
return json_dispatch(variant, privileged_dispatch_table, flags, userdata);
}
static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch binding_dispatch_table[] = {
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(GroupRecord, gid), 0 },
{},
};
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));
if (!m)
return 0;
return json_dispatch(m, binding_dispatch_table, flags, userdata);
}
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 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(GroupRecord, gid), 0 },
{ "members", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, members), JSON_RELAX},
{ "administrators", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, administrators), JSON_RELAX},
{},
};
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, 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[] = {
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(GroupRecord, service), JSON_SAFE },
{},
};
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));
if (!m)
return 0;
return json_dispatch(m, status_dispatch_table, flags, userdata);
}
static int group_record_augment(GroupRecord *h, JsonDispatchFlags json_flags) {
assert(h);
if (!FLAGS_SET(h->mask, USER_RECORD_REGULAR))
return 0;
assert(h->group_name);
if (!h->group_name_and_realm_auto && h->realm) {
h->group_name_and_realm_auto = strjoin(h->group_name, "@", h->realm);
if (!h->group_name_and_realm_auto)
return json_log_oom(h->json, json_flags);
}
return 0;
}
int group_record_load(
GroupRecord *h,
JsonVariant *v,
UserRecordLoadFlags load_flags) {
static const JsonDispatch group_dispatch_table[] = {
{ "groupName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(GroupRecord, group_name), JSON_RELAX},
{ "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(GroupRecord, realm), 0 },
{ "description", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(GroupRecord, description), 0 },
{ "disposition", JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(GroupRecord, disposition), 0 },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(GroupRecord, service), JSON_SAFE },
{ "lastChangeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(GroupRecord, last_change_usec), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(GroupRecord, gid), 0 },
{ "members", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, members), JSON_RELAX},
{ "administrators", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, administrators), JSON_RELAX},
{ "privileged", JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
/* Not defined for now, for groups, but let's at least generate sensible errors about it */
{ "secret", JSON_VARIANT_OBJECT, json_dispatch_unsupported, 0, 0 },
/* Ignore the perMachine, binding and 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! */
if ((USER_RECORD_REQUIRE_MASK(load_flags) & (USER_RECORD_SECRET|USER_RECORD_PRIVILEGED)))
return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Secret and privileged section currently not available for groups, refusing.");
r = user_group_record_mangle(v, load_flags, &h->json, &h->mask);
if (r < 0)
return r;
r = json_dispatch(h->json, group_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, 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->group_name)
return json_log(h->json, json_flags, SYNTHETIC_ERRNO(EINVAL), "Group name field missing, refusing.");
r = group_record_augment(h, json_flags);
if (r < 0)
return r;
return 0;
}
int group_record_build(GroupRecord **ret, ...) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(group_record_unrefp) GroupRecord *g = 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;
g = group_record_new();
if (!g)
return -ENOMEM;
r = group_record_load(g, v, USER_RECORD_LOAD_FULL);
if (r < 0)
return r;
*ret = TAKE_PTR(g);
return 0;
}
const char *group_record_group_name_and_realm(GroupRecord *h) {
assert(h);
/* Return the pre-initialized joined string if it is defined */
if (h->group_name_and_realm_auto)
return h->group_name_and_realm_auto;
/* If it's not defined then we cannot have a realm */
assert(!h->realm);
return h->group_name;
}
UserDisposition group_record_disposition(GroupRecord *h) {
assert(h);
if (h->disposition >= 0)
return h->disposition;
/* If not declared, derive from GID */
if (!gid_is_valid(h->gid))
return _USER_DISPOSITION_INVALID;
if (h->gid == 0 || h->gid == GID_NOBODY)
return USER_INTRINSIC;
if (gid_is_system(h->gid))
return USER_SYSTEM;
if (gid_is_dynamic(h->gid))
return USER_DYNAMIC;
if (gid_is_container(h->gid))
return USER_CONTAINER;
if (h->gid > INT32_MAX)
return USER_RESERVED;
return USER_REGULAR;
}
int group_record_clone(GroupRecord *h, UserRecordLoadFlags flags, GroupRecord **ret) {
_cleanup_(group_record_unrefp) GroupRecord *c = NULL;
int r;
assert(h);
assert(ret);
c = group_record_new();
if (!c)
return -ENOMEM;
r = group_record_load(c, h->json, flags);
if (r < 0)
return r;
*ret = TAKE_PTR(c);
return 0;
}