| /* 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; |
| } |