1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "group-record.h"
7 GroupRecord
* group_record_new(void) {
10 h
= new(GroupRecord
, 1);
16 .disposition
= _USER_DISPOSITION_INVALID
,
17 .last_change_usec
= UINT64_MAX
,
24 static GroupRecord
*group_record_free(GroupRecord
*g
) {
30 free(g
->group_name_and_realm_auto
);
33 strv_free(g
->members
);
35 strv_free(g
->administrators
);
36 strv_free_erase(g
->hashed_password
);
38 json_variant_unref(g
->json
);
43 DEFINE_TRIVIAL_REF_UNREF_FUNC(GroupRecord
, group_record
, group_record_free
);
45 static int dispatch_privileged(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
47 static const JsonDispatch privileged_dispatch_table
[] = {
48 { "hashedPassword", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_strv
, offsetof(GroupRecord
, hashed_password
), JSON_SAFE
},
52 return json_dispatch(variant
, privileged_dispatch_table
, NULL
, flags
, userdata
);
55 static int dispatch_binding(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
57 static const JsonDispatch binding_dispatch_table
[] = {
58 { "gid", JSON_VARIANT_UNSIGNED
, json_dispatch_uid_gid
, offsetof(GroupRecord
, gid
), 0 },
62 char smid
[SD_ID128_STRING_MAX
];
70 if (!json_variant_is_object(variant
))
71 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not an object.", strna(name
));
73 r
= sd_id128_get_machine(&mid
);
75 return json_log(variant
, flags
, r
, "Failed to determine machine ID: %m");
77 m
= json_variant_by_key(variant
, sd_id128_to_string(mid
, smid
));
81 return json_dispatch(m
, binding_dispatch_table
, NULL
, flags
, userdata
);
84 static int dispatch_per_machine(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
86 static const JsonDispatch per_machine_dispatch_table
[] = {
87 { "matchMachineId", _JSON_VARIANT_TYPE_INVALID
, NULL
, 0, 0 },
88 { "matchHostname", _JSON_VARIANT_TYPE_INVALID
, NULL
, 0, 0 },
89 { "gid", JSON_VARIANT_UNSIGNED
, json_dispatch_uid_gid
, offsetof(GroupRecord
, gid
), 0 },
90 { "members", JSON_VARIANT_ARRAY
, json_dispatch_user_group_list
, offsetof(GroupRecord
, members
), JSON_RELAX
},
91 { "administrators", JSON_VARIANT_ARRAY
, json_dispatch_user_group_list
, offsetof(GroupRecord
, administrators
), JSON_RELAX
},
101 if (!json_variant_is_array(variant
))
102 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not an array.", strna(name
));
104 JSON_VARIANT_ARRAY_FOREACH(e
, variant
) {
105 bool matching
= false;
108 if (!json_variant_is_object(e
))
109 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not an array of objects.", strna(name
));
111 m
= json_variant_by_key(e
, "matchMachineId");
113 r
= per_machine_id_match(m
, flags
);
121 m
= json_variant_by_key(e
, "matchHostname");
123 r
= per_machine_hostname_match(m
, flags
);
134 r
= json_dispatch(e
, per_machine_dispatch_table
, NULL
, flags
, userdata
);
142 static int dispatch_status(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
144 static const JsonDispatch status_dispatch_table
[] = {
145 { "service", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(GroupRecord
, service
), JSON_SAFE
},
149 char smid
[SD_ID128_STRING_MAX
];
157 if (!json_variant_is_object(variant
))
158 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not an object.", strna(name
));
160 r
= sd_id128_get_machine(&mid
);
162 return json_log(variant
, flags
, r
, "Failed to determine machine ID: %m");
164 m
= json_variant_by_key(variant
, sd_id128_to_string(mid
, smid
));
168 return json_dispatch(m
, status_dispatch_table
, NULL
, flags
, userdata
);
171 static int group_record_augment(GroupRecord
*h
, JsonDispatchFlags json_flags
) {
174 if (!FLAGS_SET(h
->mask
, USER_RECORD_REGULAR
))
177 assert(h
->group_name
);
179 if (!h
->group_name_and_realm_auto
&& h
->realm
) {
180 h
->group_name_and_realm_auto
= strjoin(h
->group_name
, "@", h
->realm
);
181 if (!h
->group_name_and_realm_auto
)
182 return json_log_oom(h
->json
, json_flags
);
188 int group_record_load(
191 UserRecordLoadFlags load_flags
) {
193 static const JsonDispatch group_dispatch_table
[] = {
194 { "groupName", JSON_VARIANT_STRING
, json_dispatch_user_group_name
, offsetof(GroupRecord
, group_name
), JSON_RELAX
},
195 { "realm", JSON_VARIANT_STRING
, json_dispatch_realm
, offsetof(GroupRecord
, realm
), 0 },
196 { "description", JSON_VARIANT_STRING
, json_dispatch_gecos
, offsetof(GroupRecord
, description
), 0 },
197 { "disposition", JSON_VARIANT_STRING
, json_dispatch_user_disposition
, offsetof(GroupRecord
, disposition
), 0 },
198 { "service", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(GroupRecord
, service
), JSON_SAFE
},
199 { "lastChangeUSec", JSON_VARIANT_UNSIGNED
, json_dispatch_uint64
, offsetof(GroupRecord
, last_change_usec
), 0 },
200 { "gid", JSON_VARIANT_UNSIGNED
, json_dispatch_uid_gid
, offsetof(GroupRecord
, gid
), 0 },
201 { "members", JSON_VARIANT_ARRAY
, json_dispatch_user_group_list
, offsetof(GroupRecord
, members
), JSON_RELAX
},
202 { "administrators", JSON_VARIANT_ARRAY
, json_dispatch_user_group_list
, offsetof(GroupRecord
, administrators
), JSON_RELAX
},
204 { "privileged", JSON_VARIANT_OBJECT
, dispatch_privileged
, 0, 0 },
206 /* Not defined for now, for groups, but let's at least generate sensible errors about it */
207 { "secret", JSON_VARIANT_OBJECT
, json_dispatch_unsupported
, 0, 0 },
209 /* Ignore the perMachine, binding and status stuff here, and process it later, so that it overrides whatever is set above */
210 { "perMachine", JSON_VARIANT_ARRAY
, NULL
, 0, 0 },
211 { "binding", JSON_VARIANT_OBJECT
, NULL
, 0, 0 },
212 { "status", JSON_VARIANT_OBJECT
, NULL
, 0, 0 },
214 /* Ignore 'signature', we check it with explicit accessors instead */
215 { "signature", JSON_VARIANT_ARRAY
, NULL
, 0, 0 },
219 JsonDispatchFlags json_flags
= USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags
);
225 /* Note that this call will leave a half-initialized record around on failure! */
227 if ((USER_RECORD_REQUIRE_MASK(load_flags
) & (USER_RECORD_SECRET
|USER_RECORD_PRIVILEGED
)))
228 return json_log(v
, json_flags
, SYNTHETIC_ERRNO(EINVAL
), "Secret and privileged section currently not available for groups, refusing.");
230 r
= user_group_record_mangle(v
, load_flags
, &h
->json
, &h
->mask
);
234 r
= json_dispatch(h
->json
, group_dispatch_table
, NULL
, json_flags
, h
);
238 /* During the parsing operation above we ignored the 'perMachine', 'binding' and 'status' fields, since we want
239 * them to override the global options. Let's process them now. */
241 r
= dispatch_per_machine("perMachine", json_variant_by_key(h
->json
, "perMachine"), json_flags
, h
);
245 r
= dispatch_binding("binding", json_variant_by_key(h
->json
, "binding"), json_flags
, h
);
249 r
= dispatch_status("status", json_variant_by_key(h
->json
, "status"), json_flags
, h
);
253 if (FLAGS_SET(h
->mask
, USER_RECORD_REGULAR
) && !h
->group_name
)
254 return json_log(h
->json
, json_flags
, SYNTHETIC_ERRNO(EINVAL
), "Group name field missing, refusing.");
256 r
= group_record_augment(h
, json_flags
);
263 int group_record_build(GroupRecord
**ret
, ...) {
264 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
265 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
272 r
= json_buildv(&v
, ap
);
278 g
= group_record_new();
282 r
= group_record_load(g
, v
, USER_RECORD_LOAD_FULL
);
290 const char *group_record_group_name_and_realm(GroupRecord
*h
) {
293 /* Return the pre-initialized joined string if it is defined */
294 if (h
->group_name_and_realm_auto
)
295 return h
->group_name_and_realm_auto
;
297 /* If it's not defined then we cannot have a realm */
299 return h
->group_name
;
302 UserDisposition
group_record_disposition(GroupRecord
*h
) {
305 if (h
->disposition
>= 0)
306 return h
->disposition
;
308 /* If not declared, derive from GID */
310 if (!gid_is_valid(h
->gid
))
311 return _USER_DISPOSITION_INVALID
;
313 if (h
->gid
== 0 || h
->gid
== GID_NOBODY
)
314 return USER_INTRINSIC
;
316 if (gid_is_system(h
->gid
))
319 if (gid_is_dynamic(h
->gid
))
322 if (gid_is_container(h
->gid
))
323 return USER_CONTAINER
;
325 if (h
->gid
> INT32_MAX
)
326 return USER_RESERVED
;
331 int group_record_clone(GroupRecord
*h
, UserRecordLoadFlags flags
, GroupRecord
**ret
) {
332 _cleanup_(group_record_unrefp
) GroupRecord
*c
= NULL
;
338 c
= group_record_new();
342 r
= group_record_load(c
, h
->json
, flags
);