]>
Commit | Line | Data |
---|---|---|
70a5db58 LP |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | ||
3 | #include "group-record.h" | |
4 | #include "homed-varlink.h" | |
5 | #include "strv.h" | |
6 | #include "user-record-util.h" | |
7 | #include "user-record.h" | |
8 | #include "user-util.h" | |
9 | #include "format-util.h" | |
10 | ||
11 | typedef struct LookupParameters { | |
12 | const char *user_name; | |
13 | const char *group_name; | |
14 | union { | |
15 | uid_t uid; | |
16 | gid_t gid; | |
17 | }; | |
18 | const char *service; | |
19 | } LookupParameters; | |
20 | ||
21 | static bool client_is_trusted(Varlink *link, Home *h) { | |
22 | uid_t peer_uid; | |
23 | int r; | |
24 | ||
25 | assert(link); | |
26 | assert(h); | |
27 | ||
28 | r = varlink_get_peer_uid(link, &peer_uid); | |
29 | if (r < 0) { | |
30 | log_debug_errno(r, "Unable to query peer UID, ignoring: %m"); | |
31 | return false; | |
32 | } | |
33 | ||
34 | return peer_uid == 0 || peer_uid == h->uid; | |
35 | } | |
36 | ||
37 | static int build_user_json(Home *h, bool trusted, JsonVariant **ret) { | |
38 | _cleanup_(user_record_unrefp) UserRecord *augmented = NULL; | |
39 | UserRecordLoadFlags flags; | |
40 | int r; | |
41 | ||
42 | assert(h); | |
43 | assert(ret); | |
44 | ||
45 | flags = USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_BINDING|USER_RECORD_STRIP_SECRET|USER_RECORD_ALLOW_STATUS|USER_RECORD_ALLOW_SIGNATURE; | |
46 | if (trusted) | |
47 | flags |= USER_RECORD_ALLOW_PRIVILEGED; | |
48 | else | |
49 | flags |= USER_RECORD_STRIP_PRIVILEGED; | |
50 | ||
51 | r = home_augment_status(h, flags, &augmented); | |
52 | if (r < 0) | |
53 | return r; | |
54 | ||
55 | return json_build(ret, JSON_BUILD_OBJECT( | |
56 | JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(augmented->json)), | |
57 | JSON_BUILD_PAIR("incomplete", JSON_BUILD_BOOLEAN(augmented->incomplete)))); | |
58 | } | |
59 | ||
60 | static bool home_user_match_lookup_parameters(LookupParameters *p, Home *h) { | |
61 | assert(p); | |
62 | assert(h); | |
63 | ||
64 | if (p->user_name && !streq(p->user_name, h->user_name)) | |
65 | return false; | |
66 | ||
67 | if (uid_is_valid(p->uid) && h->uid != p->uid) | |
68 | return false; | |
69 | ||
70 | return true; | |
71 | } | |
72 | ||
73 | int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
74 | ||
75 | static const JsonDispatch dispatch_table[] = { | |
76 | { "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 }, | |
77 | { "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), JSON_SAFE }, | |
78 | { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, | |
79 | {} | |
80 | }; | |
81 | ||
82 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; | |
83 | LookupParameters p = { | |
84 | .uid = UID_INVALID, | |
85 | }; | |
86 | Manager *m = userdata; | |
87 | bool trusted; | |
88 | Home *h; | |
89 | int r; | |
90 | ||
91 | assert(parameters); | |
92 | assert(m); | |
93 | ||
94 | r = json_dispatch(parameters, dispatch_table, NULL, 0, &p); | |
95 | if (r < 0) | |
96 | return r; | |
97 | ||
98 | if (!streq_ptr(p.service, "io.systemd.Home")) | |
99 | return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); | |
100 | ||
101 | if (uid_is_valid(p.uid)) | |
102 | h = hashmap_get(m->homes_by_uid, UID_TO_PTR(p.uid)); | |
103 | else if (p.user_name) | |
104 | h = hashmap_get(m->homes_by_name, p.user_name); | |
105 | else { | |
106 | Iterator i; | |
107 | ||
108 | /* If neither UID nor name was specified, then dump all homes. Do so with varlink_notify() | |
109 | * for all entries but the last, so that clients can stream the results, and easily process | |
110 | * them piecemeal. */ | |
111 | ||
112 | HASHMAP_FOREACH(h, m->homes_by_name, i) { | |
113 | ||
114 | if (!home_user_match_lookup_parameters(&p, h)) | |
115 | continue; | |
116 | ||
117 | if (v) { | |
118 | /* An entry set from the previous iteration? Then send it now */ | |
119 | r = varlink_notify(link, v); | |
120 | if (r < 0) | |
121 | return r; | |
122 | ||
123 | v = json_variant_unref(v); | |
124 | } | |
125 | ||
126 | trusted = client_is_trusted(link, h); | |
127 | ||
128 | r = build_user_json(h, trusted, &v); | |
129 | if (r < 0) | |
130 | return r; | |
131 | } | |
132 | ||
133 | if (!v) | |
134 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
135 | ||
136 | return varlink_reply(link, v); | |
137 | } | |
138 | ||
139 | if (!h) | |
140 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
141 | ||
142 | if (!home_user_match_lookup_parameters(&p, h)) | |
143 | return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); | |
144 | ||
145 | trusted = client_is_trusted(link, h); | |
146 | ||
147 | r = build_user_json(h, trusted, &v); | |
148 | if (r < 0) | |
149 | return r; | |
150 | ||
151 | return varlink_reply(link, v); | |
152 | } | |
153 | ||
154 | static int build_group_json(Home *h, JsonVariant **ret) { | |
155 | _cleanup_(group_record_unrefp) GroupRecord *g = NULL; | |
156 | int r; | |
157 | ||
158 | assert(h); | |
159 | assert(ret); | |
160 | ||
161 | g = group_record_new(); | |
162 | if (!g) | |
163 | return -ENOMEM; | |
164 | ||
165 | r = group_record_synthesize(g, h->record); | |
166 | if (r < 0) | |
167 | return r; | |
168 | ||
169 | assert(!FLAGS_SET(g->mask, USER_RECORD_SECRET)); | |
170 | assert(!FLAGS_SET(g->mask, USER_RECORD_PRIVILEGED)); | |
171 | ||
172 | return json_build(ret, | |
173 | JSON_BUILD_OBJECT( | |
174 | JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(g->json)))); | |
175 | } | |
176 | ||
177 | static bool home_group_match_lookup_parameters(LookupParameters *p, Home *h) { | |
178 | assert(p); | |
179 | assert(h); | |
180 | ||
181 | if (p->group_name && !streq(h->user_name, p->group_name)) | |
182 | return false; | |
183 | ||
184 | if (gid_is_valid(p->gid) && h->uid != (uid_t) p->gid) | |
185 | return false; | |
186 | ||
187 | return true; | |
188 | } | |
189 | ||
190 | int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
191 | ||
192 | static const JsonDispatch dispatch_table[] = { | |
193 | { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 }, | |
194 | { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), JSON_SAFE }, | |
195 | { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, | |
196 | {} | |
197 | }; | |
198 | ||
199 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; | |
200 | LookupParameters p = { | |
201 | .gid = GID_INVALID, | |
202 | }; | |
203 | Manager *m = userdata; | |
204 | Home *h; | |
205 | int r; | |
206 | ||
207 | assert(parameters); | |
208 | assert(m); | |
209 | ||
210 | r = json_dispatch(parameters, dispatch_table, NULL, 0, &p); | |
211 | if (r < 0) | |
212 | return r; | |
213 | ||
214 | if (!streq_ptr(p.service, "io.systemd.Home")) | |
215 | return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); | |
216 | ||
217 | if (gid_is_valid(p.gid)) | |
218 | h = hashmap_get(m->homes_by_uid, UID_TO_PTR((uid_t) p.gid)); | |
219 | else if (p.group_name) | |
220 | h = hashmap_get(m->homes_by_name, p.group_name); | |
221 | else { | |
222 | Iterator i; | |
223 | ||
224 | HASHMAP_FOREACH(h, m->homes_by_name, i) { | |
225 | ||
226 | if (!home_group_match_lookup_parameters(&p, h)) | |
227 | continue; | |
228 | ||
229 | if (v) { | |
230 | r = varlink_notify(link, v); | |
231 | if (r < 0) | |
232 | return r; | |
233 | ||
234 | v = json_variant_unref(v); | |
235 | } | |
236 | ||
237 | r = build_group_json(h, &v); | |
238 | if (r < 0) | |
239 | return r; | |
240 | } | |
241 | ||
242 | if (!v) | |
243 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
244 | ||
245 | return varlink_reply(link, v); | |
246 | } | |
247 | ||
248 | if (!h) | |
249 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
250 | ||
251 | if (!home_group_match_lookup_parameters(&p, h)) | |
252 | return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); | |
253 | ||
254 | r = build_group_json(h, &v); | |
255 | if (r < 0) | |
256 | return r; | |
257 | ||
258 | return varlink_reply(link, v); | |
259 | } | |
260 | ||
261 | int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
262 | ||
263 | static const JsonDispatch dispatch_table[] = { | |
264 | { "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), JSON_SAFE }, | |
265 | { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), JSON_SAFE }, | |
266 | { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, | |
267 | {} | |
268 | }; | |
269 | ||
270 | Manager *m = userdata; | |
271 | LookupParameters p = {}; | |
272 | Home *h; | |
273 | int r; | |
274 | ||
275 | assert(parameters); | |
276 | assert(m); | |
277 | ||
278 | r = json_dispatch(parameters, dispatch_table, NULL, 0, &p); | |
279 | if (r < 0) | |
280 | return r; | |
281 | ||
282 | if (!streq_ptr(p.service, "io.systemd.Home")) | |
283 | return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); | |
284 | ||
285 | if (p.user_name) { | |
286 | const char *last = NULL; | |
287 | char **i; | |
288 | ||
289 | h = hashmap_get(m->homes_by_name, p.user_name); | |
290 | if (!h) | |
291 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
292 | ||
293 | if (p.group_name) { | |
294 | if (!strv_contains(h->record->member_of, p.group_name)) | |
295 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
296 | ||
297 | return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)), | |
298 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name)))); | |
299 | } | |
300 | ||
301 | STRV_FOREACH(i, h->record->member_of) { | |
302 | if (last) { | |
303 | r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)), | |
304 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last)))); | |
305 | if (r < 0) | |
306 | return r; | |
307 | } | |
308 | ||
309 | last = *i; | |
310 | } | |
311 | ||
312 | if (last) | |
313 | return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)), | |
314 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last)))); | |
315 | ||
316 | } else if (p.group_name) { | |
317 | const char *last = NULL; | |
318 | Iterator i; | |
319 | ||
320 | HASHMAP_FOREACH(h, m->homes_by_name, i) { | |
321 | ||
322 | if (!strv_contains(h->record->member_of, p.group_name)) | |
323 | continue; | |
324 | ||
325 | if (last) { | |
326 | r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last)), | |
327 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name)))); | |
328 | if (r < 0) | |
329 | return r; | |
330 | } | |
331 | ||
332 | last = h->user_name; | |
333 | } | |
334 | ||
335 | if (last) | |
336 | return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last)), | |
337 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name)))); | |
338 | } else { | |
339 | const char *last_user_name = NULL, *last_group_name = NULL; | |
340 | Iterator i; | |
341 | ||
342 | HASHMAP_FOREACH(h, m->homes_by_name, i) { | |
343 | char **j; | |
344 | ||
345 | STRV_FOREACH(j, h->record->member_of) { | |
346 | ||
347 | if (last_user_name) { | |
348 | assert(last_group_name); | |
349 | ||
350 | r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)), | |
351 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name)))); | |
352 | ||
353 | if (r < 0) | |
354 | return r; | |
355 | } | |
356 | ||
357 | last_user_name = h->user_name; | |
358 | last_group_name = *j; | |
359 | } | |
360 | } | |
361 | ||
362 | if (last_user_name) { | |
363 | assert(last_group_name); | |
364 | return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)), | |
365 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name)))); | |
366 | } | |
367 | } | |
368 | ||
369 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
370 | } |