1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "group-record.h"
4 #include "strv.h"
5 #include "uid-alloc-range.h"
6 #include "user-util.h"
7
group_record_new(void)8 GroupRecord* group_record_new(void) {
9 GroupRecord *h;
10
11 h = new(GroupRecord, 1);
12 if (!h)
13 return NULL;
14
15 *h = (GroupRecord) {
16 .n_ref = 1,
17 .disposition = _USER_DISPOSITION_INVALID,
18 .last_change_usec = UINT64_MAX,
19 .gid = GID_INVALID,
20 };
21
22 return h;
23 }
24
group_record_free(GroupRecord * g)25 static GroupRecord *group_record_free(GroupRecord *g) {
26 if (!g)
27 return NULL;
28
29 free(g->group_name);
30 free(g->realm);
31 free(g->group_name_and_realm_auto);
32 free(g->description);
33
34 strv_free(g->members);
35 free(g->service);
36 strv_free(g->administrators);
37 strv_free_erase(g->hashed_password);
38
39 json_variant_unref(g->json);
40
41 return mfree(g);
42 }
43
44 DEFINE_TRIVIAL_REF_UNREF_FUNC(GroupRecord, group_record, group_record_free);
45
dispatch_privileged(const char * name,JsonVariant * variant,JsonDispatchFlags flags,void * userdata)46 static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
47
48 static const JsonDispatch privileged_dispatch_table[] = {
49 { "hashedPassword", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(GroupRecord, hashed_password), JSON_SAFE },
50 {},
51 };
52
53 return json_dispatch(variant, privileged_dispatch_table, NULL, flags, userdata);
54 }
55
dispatch_binding(const char * name,JsonVariant * variant,JsonDispatchFlags flags,void * userdata)56 static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
57
58 static const JsonDispatch binding_dispatch_table[] = {
59 { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(GroupRecord, gid), 0 },
60 {},
61 };
62
63 JsonVariant *m;
64 sd_id128_t mid;
65 int r;
66
67 if (!variant)
68 return 0;
69
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));
72
73 r = sd_id128_get_machine(&mid);
74 if (r < 0)
75 return json_log(variant, flags, r, "Failed to determine machine ID: %m");
76
77 m = json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
78 if (!m)
79 return 0;
80
81 return json_dispatch(m, binding_dispatch_table, NULL, flags, userdata);
82 }
83
dispatch_per_machine(const char * name,JsonVariant * variant,JsonDispatchFlags flags,void * userdata)84 static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
85
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},
92 {},
93 };
94
95 JsonVariant *e;
96 int r;
97
98 if (!variant)
99 return 0;
100
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));
103
104 JSON_VARIANT_ARRAY_FOREACH(e, variant) {
105 bool matching = false;
106 JsonVariant *m;
107
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));
110
111 m = json_variant_by_key(e, "matchMachineId");
112 if (m) {
113 r = per_machine_id_match(m, flags);
114 if (r < 0)
115 return r;
116
117 matching = r > 0;
118 }
119
120 if (!matching) {
121 m = json_variant_by_key(e, "matchHostname");
122 if (m) {
123 r = per_machine_hostname_match(m, flags);
124 if (r < 0)
125 return r;
126
127 matching = r > 0;
128 }
129 }
130
131 if (!matching)
132 continue;
133
134 r = json_dispatch(e, per_machine_dispatch_table, NULL, flags, userdata);
135 if (r < 0)
136 return r;
137 }
138
139 return 0;
140 }
141
dispatch_status(const char * name,JsonVariant * variant,JsonDispatchFlags flags,void * userdata)142 static int dispatch_status(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
143
144 static const JsonDispatch status_dispatch_table[] = {
145 { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(GroupRecord, service), JSON_SAFE },
146 {},
147 };
148
149 JsonVariant *m;
150 sd_id128_t mid;
151 int r;
152
153 if (!variant)
154 return 0;
155
156 if (!json_variant_is_object(variant))
157 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
158
159 r = sd_id128_get_machine(&mid);
160 if (r < 0)
161 return json_log(variant, flags, r, "Failed to determine machine ID: %m");
162
163 m = json_variant_by_key(variant, SD_ID128_TO_STRING(mid));
164 if (!m)
165 return 0;
166
167 return json_dispatch(m, status_dispatch_table, NULL, flags, userdata);
168 }
169
group_record_augment(GroupRecord * h,JsonDispatchFlags json_flags)170 static int group_record_augment(GroupRecord *h, JsonDispatchFlags json_flags) {
171 assert(h);
172
173 if (!FLAGS_SET(h->mask, USER_RECORD_REGULAR))
174 return 0;
175
176 assert(h->group_name);
177
178 if (!h->group_name_and_realm_auto && h->realm) {
179 h->group_name_and_realm_auto = strjoin(h->group_name, "@", h->realm);
180 if (!h->group_name_and_realm_auto)
181 return json_log_oom(h->json, json_flags);
182 }
183
184 return 0;
185 }
186
group_record_load(GroupRecord * h,JsonVariant * v,UserRecordLoadFlags load_flags)187 int group_record_load(
188 GroupRecord *h,
189 JsonVariant *v,
190 UserRecordLoadFlags load_flags) {
191
192 static const JsonDispatch group_dispatch_table[] = {
193 { "groupName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(GroupRecord, group_name), JSON_RELAX},
194 { "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(GroupRecord, realm), 0 },
195 { "description", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(GroupRecord, description), 0 },
196 { "disposition", JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(GroupRecord, disposition), 0 },
197 { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(GroupRecord, service), JSON_SAFE },
198 { "lastChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(GroupRecord, last_change_usec), 0 },
199 { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(GroupRecord, gid), 0 },
200 { "members", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, members), JSON_RELAX},
201 { "administrators", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, administrators), JSON_RELAX},
202
203 { "privileged", JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
204
205 /* Not defined for now, for groups, but let's at least generate sensible errors about it */
206 { "secret", JSON_VARIANT_OBJECT, json_dispatch_unsupported, 0, 0 },
207
208 /* Ignore the perMachine, binding and status stuff here, and process it later, so that it overrides whatever is set above */
209 { "perMachine", JSON_VARIANT_ARRAY, NULL, 0, 0 },
210 { "binding", JSON_VARIANT_OBJECT, NULL, 0, 0 },
211 { "status", JSON_VARIANT_OBJECT, NULL, 0, 0 },
212
213 /* Ignore 'signature', we check it with explicit accessors instead */
214 { "signature", JSON_VARIANT_ARRAY, NULL, 0, 0 },
215 {},
216 };
217
218 JsonDispatchFlags json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
219 int r;
220
221 assert(h);
222 assert(!h->json);
223
224 /* Note that this call will leave a half-initialized record around on failure! */
225
226 if ((USER_RECORD_REQUIRE_MASK(load_flags) & (USER_RECORD_SECRET|USER_RECORD_PRIVILEGED)))
227 return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Secret and privileged section currently not available for groups, refusing.");
228
229 r = user_group_record_mangle(v, load_flags, &h->json, &h->mask);
230 if (r < 0)
231 return r;
232
233 r = json_dispatch(h->json, group_dispatch_table, NULL, json_flags, h);
234 if (r < 0)
235 return r;
236
237 /* During the parsing operation above we ignored the 'perMachine', 'binding' and 'status' fields, since we want
238 * them to override the global options. Let's process them now. */
239
240 r = dispatch_per_machine("perMachine", json_variant_by_key(h->json, "perMachine"), json_flags, h);
241 if (r < 0)
242 return r;
243
244 r = dispatch_binding("binding", json_variant_by_key(h->json, "binding"), json_flags, h);
245 if (r < 0)
246 return r;
247
248 r = dispatch_status("status", json_variant_by_key(h->json, "status"), json_flags, h);
249 if (r < 0)
250 return r;
251
252 if (FLAGS_SET(h->mask, USER_RECORD_REGULAR) && !h->group_name)
253 return json_log(h->json, json_flags, SYNTHETIC_ERRNO(EINVAL), "Group name field missing, refusing.");
254
255 r = group_record_augment(h, json_flags);
256 if (r < 0)
257 return r;
258
259 return 0;
260 }
261
group_record_build(GroupRecord ** ret,...)262 int group_record_build(GroupRecord **ret, ...) {
263 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
264 _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
265 va_list ap;
266 int r;
267
268 assert(ret);
269
270 va_start(ap, ret);
271 r = json_buildv(&v, ap);
272 va_end(ap);
273
274 if (r < 0)
275 return r;
276
277 g = group_record_new();
278 if (!g)
279 return -ENOMEM;
280
281 r = group_record_load(g, v, USER_RECORD_LOAD_FULL);
282 if (r < 0)
283 return r;
284
285 *ret = TAKE_PTR(g);
286 return 0;
287 }
288
group_record_group_name_and_realm(GroupRecord * h)289 const char *group_record_group_name_and_realm(GroupRecord *h) {
290 assert(h);
291
292 /* Return the pre-initialized joined string if it is defined */
293 if (h->group_name_and_realm_auto)
294 return h->group_name_and_realm_auto;
295
296 /* If it's not defined then we cannot have a realm */
297 assert(!h->realm);
298 return h->group_name;
299 }
300
group_record_disposition(GroupRecord * h)301 UserDisposition group_record_disposition(GroupRecord *h) {
302 assert(h);
303
304 if (h->disposition >= 0)
305 return h->disposition;
306
307 /* If not declared, derive from GID */
308
309 if (!gid_is_valid(h->gid))
310 return _USER_DISPOSITION_INVALID;
311
312 if (h->gid == 0 || h->gid == GID_NOBODY)
313 return USER_INTRINSIC;
314
315 if (gid_is_system(h->gid))
316 return USER_SYSTEM;
317
318 if (gid_is_dynamic(h->gid))
319 return USER_DYNAMIC;
320
321 if (gid_is_container(h->gid))
322 return USER_CONTAINER;
323
324 if (h->gid > INT32_MAX)
325 return USER_RESERVED;
326
327 return USER_REGULAR;
328 }
329
group_record_clone(GroupRecord * h,UserRecordLoadFlags flags,GroupRecord ** ret)330 int group_record_clone(GroupRecord *h, UserRecordLoadFlags flags, GroupRecord **ret) {
331 _cleanup_(group_record_unrefp) GroupRecord *c = NULL;
332 int r;
333
334 assert(h);
335 assert(ret);
336
337 c = group_record_new();
338 if (!c)
339 return -ENOMEM;
340
341 r = group_record_load(c, h->json, flags);
342 if (r < 0)
343 return r;
344
345 *ret = TAKE_PTR(c);
346 return 0;
347 }
348