]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
71d0b9d4 LP |
2 | |
3 | #include "group-record.h" | |
4 | #include "strv.h" | |
b085d224 | 5 | #include "uid-alloc-range.h" |
71d0b9d4 LP |
6 | #include "user-util.h" |
7 | ||
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 | ||
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); | |
0bb43080 | 32 | free(g->description); |
71d0b9d4 LP |
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 | ||
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 | ||
f1b622a0 | 53 | return json_dispatch(variant, privileged_dispatch_table, flags, userdata); |
71d0b9d4 LP |
54 | } |
55 | ||
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 | ||
71d0b9d4 LP |
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 | ||
85b55869 | 77 | m = json_variant_by_key(variant, SD_ID128_TO_STRING(mid)); |
71d0b9d4 LP |
78 | if (!m) |
79 | return 0; | |
80 | ||
f1b622a0 | 81 | return json_dispatch(m, binding_dispatch_table, flags, userdata); |
71d0b9d4 LP |
82 | } |
83 | ||
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 }, | |
7a8867ab LP |
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}, | |
71d0b9d4 LP |
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 | ||
f1b622a0 | 134 | r = json_dispatch(e, per_machine_dispatch_table, flags, userdata); |
71d0b9d4 LP |
135 | if (r < 0) |
136 | return r; | |
137 | } | |
138 | ||
139 | return 0; | |
140 | } | |
141 | ||
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 | ||
71d0b9d4 LP |
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 | ||
85b55869 | 163 | m = json_variant_by_key(variant, SD_ID128_TO_STRING(mid)); |
71d0b9d4 LP |
164 | if (!m) |
165 | return 0; | |
166 | ||
f1b622a0 | 167 | return json_dispatch(m, status_dispatch_table, flags, userdata); |
71d0b9d4 LP |
168 | } |
169 | ||
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 | ||
187 | int group_record_load( | |
188 | GroupRecord *h, | |
189 | JsonVariant *v, | |
190 | UserRecordLoadFlags load_flags) { | |
191 | ||
192 | static const JsonDispatch group_dispatch_table[] = { | |
7a8867ab | 193 | { "groupName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(GroupRecord, group_name), JSON_RELAX}, |
71d0b9d4 | 194 | { "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(GroupRecord, realm), 0 }, |
0bb43080 | 195 | { "description", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(GroupRecord, description), 0 }, |
71d0b9d4 LP |
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 }, | |
7a8867ab LP |
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}, | |
71d0b9d4 LP |
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 | ||
f1b622a0 | 233 | r = json_dispatch(h->json, group_dispatch_table, json_flags, h); |
71d0b9d4 LP |
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 | ||
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 | ||
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 | ||
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 | ||
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 | } |