Systemd/src/shared/group-record.c
Lennart Poettering 0bb4308014 userdb: add "description" field to group records
User records have the realname/gecos fields, groups never had that, but
it would really be useful to have it, hence let's add it with similar
semantics.

We enforce the same syntax as for GECOS, since it's better to start with
strict rules and losen them later instead of the opposite.
2020-08-07 08:39:18 +02:00

349 lines
13 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include "group-record.h"
#include "strv.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, NULL, 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 },
{},
};
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);
}
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, 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[] = {
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(GroupRecord, service), JSON_SAFE },
{},
};
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 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_UNSIGNED, 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, 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->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;
}