349 lines
13 KiB
C
349 lines
13 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#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;
|
|
}
|