]>
Commit | Line | Data |
---|---|---|
71d0b9d4 LP |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | ||
3 | #include "group-record.h" | |
4 | #include "strv.h" | |
5 | #include "user-util.h" | |
6 | ||
7 | GroupRecord* group_record_new(void) { | |
8 | GroupRecord *h; | |
9 | ||
10 | h = new(GroupRecord, 1); | |
11 | if (!h) | |
12 | return NULL; | |
13 | ||
14 | *h = (GroupRecord) { | |
15 | .n_ref = 1, | |
16 | .disposition = _USER_DISPOSITION_INVALID, | |
17 | .last_change_usec = UINT64_MAX, | |
18 | .gid = GID_INVALID, | |
19 | }; | |
20 | ||
21 | return h; | |
22 | } | |
23 | ||
24 | static GroupRecord *group_record_free(GroupRecord *g) { | |
25 | if (!g) | |
26 | return NULL; | |
27 | ||
28 | free(g->group_name); | |
29 | free(g->realm); | |
30 | free(g->group_name_and_realm_auto); | |
0bb43080 | 31 | free(g->description); |
71d0b9d4 LP |
32 | |
33 | strv_free(g->members); | |
34 | free(g->service); | |
35 | strv_free(g->administrators); | |
36 | strv_free_erase(g->hashed_password); | |
37 | ||
38 | json_variant_unref(g->json); | |
39 | ||
40 | return mfree(g); | |
41 | } | |
42 | ||
43 | DEFINE_TRIVIAL_REF_UNREF_FUNC(GroupRecord, group_record, group_record_free); | |
44 | ||
45 | static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { | |
46 | ||
47 | static const JsonDispatch privileged_dispatch_table[] = { | |
48 | { "hashedPassword", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(GroupRecord, hashed_password), JSON_SAFE }, | |
49 | {}, | |
50 | }; | |
51 | ||
52 | return json_dispatch(variant, privileged_dispatch_table, NULL, flags, userdata); | |
53 | } | |
54 | ||
55 | static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { | |
56 | ||
57 | static const JsonDispatch binding_dispatch_table[] = { | |
58 | { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(GroupRecord, gid), 0 }, | |
59 | {}, | |
60 | }; | |
61 | ||
62 | char smid[SD_ID128_STRING_MAX]; | |
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, smid)); | |
78 | if (!m) | |
79 | return 0; | |
80 | ||
81 | return json_dispatch(m, binding_dispatch_table, NULL, flags, userdata); | |
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 | ||
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 | ||
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 | char smid[SD_ID128_STRING_MAX]; | |
150 | JsonVariant *m; | |
151 | sd_id128_t mid; | |
152 | int r; | |
153 | ||
154 | if (!variant) | |
155 | return 0; | |
156 | ||
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)); | |
159 | ||
160 | r = sd_id128_get_machine(&mid); | |
161 | if (r < 0) | |
162 | return json_log(variant, flags, r, "Failed to determine machine ID: %m"); | |
163 | ||
164 | m = json_variant_by_key(variant, sd_id128_to_string(mid, smid)); | |
165 | if (!m) | |
166 | return 0; | |
167 | ||
168 | return json_dispatch(m, status_dispatch_table, NULL, flags, userdata); | |
169 | } | |
170 | ||
171 | static int group_record_augment(GroupRecord *h, JsonDispatchFlags json_flags) { | |
172 | assert(h); | |
173 | ||
174 | if (!FLAGS_SET(h->mask, USER_RECORD_REGULAR)) | |
175 | return 0; | |
176 | ||
177 | assert(h->group_name); | |
178 | ||
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); | |
183 | } | |
184 | ||
185 | return 0; | |
186 | } | |
187 | ||
188 | int group_record_load( | |
189 | GroupRecord *h, | |
190 | JsonVariant *v, | |
191 | UserRecordLoadFlags load_flags) { | |
192 | ||
193 | static const JsonDispatch group_dispatch_table[] = { | |
7a8867ab | 194 | { "groupName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(GroupRecord, group_name), JSON_RELAX}, |
71d0b9d4 | 195 | { "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(GroupRecord, realm), 0 }, |
0bb43080 | 196 | { "description", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(GroupRecord, description), 0 }, |
71d0b9d4 LP |
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 }, | |
7a8867ab LP |
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}, | |
71d0b9d4 LP |
203 | |
204 | { "privileged", JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 }, | |
205 | ||
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 }, | |
208 | ||
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 }, | |
213 | ||
214 | /* Ignore 'signature', we check it with explicit accessors instead */ | |
215 | { "signature", JSON_VARIANT_ARRAY, NULL, 0, 0 }, | |
216 | {}, | |
217 | }; | |
218 | ||
219 | JsonDispatchFlags json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags); | |
220 | int r; | |
221 | ||
222 | assert(h); | |
223 | assert(!h->json); | |
224 | ||
225 | /* Note that this call will leave a half-initialized record around on failure! */ | |
226 | ||
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."); | |
229 | ||
230 | r = user_group_record_mangle(v, load_flags, &h->json, &h->mask); | |
231 | if (r < 0) | |
232 | return r; | |
233 | ||
234 | r = json_dispatch(h->json, group_dispatch_table, NULL, json_flags, h); | |
235 | if (r < 0) | |
236 | return r; | |
237 | ||
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. */ | |
240 | ||
241 | r = dispatch_per_machine("perMachine", json_variant_by_key(h->json, "perMachine"), json_flags, h); | |
242 | if (r < 0) | |
243 | return r; | |
244 | ||
245 | r = dispatch_binding("binding", json_variant_by_key(h->json, "binding"), json_flags, h); | |
246 | if (r < 0) | |
247 | return r; | |
248 | ||
249 | r = dispatch_status("status", json_variant_by_key(h->json, "status"), json_flags, h); | |
250 | if (r < 0) | |
251 | return r; | |
252 | ||
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."); | |
255 | ||
256 | r = group_record_augment(h, json_flags); | |
257 | if (r < 0) | |
258 | return r; | |
259 | ||
260 | return 0; | |
261 | } | |
262 | ||
263 | int group_record_build(GroupRecord **ret, ...) { | |
264 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; | |
265 | _cleanup_(group_record_unrefp) GroupRecord *g = NULL; | |
266 | va_list ap; | |
267 | int r; | |
268 | ||
269 | assert(ret); | |
270 | ||
271 | va_start(ap, ret); | |
272 | r = json_buildv(&v, ap); | |
273 | va_end(ap); | |
274 | ||
275 | if (r < 0) | |
276 | return r; | |
277 | ||
278 | g = group_record_new(); | |
279 | if (!g) | |
280 | return -ENOMEM; | |
281 | ||
282 | r = group_record_load(g, v, USER_RECORD_LOAD_FULL); | |
283 | if (r < 0) | |
284 | return r; | |
285 | ||
286 | *ret = TAKE_PTR(g); | |
287 | return 0; | |
288 | } | |
289 | ||
290 | const char *group_record_group_name_and_realm(GroupRecord *h) { | |
291 | assert(h); | |
292 | ||
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; | |
296 | ||
297 | /* If it's not defined then we cannot have a realm */ | |
298 | assert(!h->realm); | |
299 | return h->group_name; | |
300 | } | |
301 | ||
302 | UserDisposition group_record_disposition(GroupRecord *h) { | |
303 | assert(h); | |
304 | ||
305 | if (h->disposition >= 0) | |
306 | return h->disposition; | |
307 | ||
308 | /* If not declared, derive from GID */ | |
309 | ||
310 | if (!gid_is_valid(h->gid)) | |
311 | return _USER_DISPOSITION_INVALID; | |
312 | ||
313 | if (h->gid == 0 || h->gid == GID_NOBODY) | |
314 | return USER_INTRINSIC; | |
315 | ||
316 | if (gid_is_system(h->gid)) | |
317 | return USER_SYSTEM; | |
318 | ||
319 | if (gid_is_dynamic(h->gid)) | |
320 | return USER_DYNAMIC; | |
321 | ||
322 | if (gid_is_container(h->gid)) | |
323 | return USER_CONTAINER; | |
324 | ||
325 | if (h->gid > INT32_MAX) | |
326 | return USER_RESERVED; | |
327 | ||
328 | return USER_REGULAR; | |
329 | } | |
330 | ||
331 | int group_record_clone(GroupRecord *h, UserRecordLoadFlags flags, GroupRecord **ret) { | |
332 | _cleanup_(group_record_unrefp) GroupRecord *c = NULL; | |
333 | int r; | |
334 | ||
335 | assert(h); | |
336 | assert(ret); | |
337 | ||
338 | c = group_record_new(); | |
339 | if (!c) | |
340 | return -ENOMEM; | |
341 | ||
342 | r = group_record_load(c, h->json, flags); | |
343 | if (r < 0) | |
344 | return r; | |
345 | ||
346 | *ret = TAKE_PTR(c); | |
347 | return 0; | |
348 | } |