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