]>
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 { | |
70a5db58 LP |
106 | |
107 | /* If neither UID nor name was specified, then dump all homes. Do so with varlink_notify() | |
108 | * for all entries but the last, so that clients can stream the results, and easily process | |
109 | * them piecemeal. */ | |
110 | ||
90e74a66 | 111 | HASHMAP_FOREACH(h, m->homes_by_name) { |
70a5db58 LP |
112 | |
113 | if (!home_user_match_lookup_parameters(&p, h)) | |
114 | continue; | |
115 | ||
116 | if (v) { | |
117 | /* An entry set from the previous iteration? Then send it now */ | |
118 | r = varlink_notify(link, v); | |
119 | if (r < 0) | |
120 | return r; | |
121 | ||
122 | v = json_variant_unref(v); | |
123 | } | |
124 | ||
125 | trusted = client_is_trusted(link, h); | |
126 | ||
127 | r = build_user_json(h, trusted, &v); | |
128 | if (r < 0) | |
129 | return r; | |
130 | } | |
131 | ||
132 | if (!v) | |
133 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
134 | ||
135 | return varlink_reply(link, v); | |
136 | } | |
137 | ||
138 | if (!h) | |
139 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
140 | ||
141 | if (!home_user_match_lookup_parameters(&p, h)) | |
142 | return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); | |
143 | ||
144 | trusted = client_is_trusted(link, h); | |
145 | ||
146 | r = build_user_json(h, trusted, &v); | |
147 | if (r < 0) | |
148 | return r; | |
149 | ||
150 | return varlink_reply(link, v); | |
151 | } | |
152 | ||
153 | static int build_group_json(Home *h, JsonVariant **ret) { | |
154 | _cleanup_(group_record_unrefp) GroupRecord *g = NULL; | |
155 | int r; | |
156 | ||
157 | assert(h); | |
158 | assert(ret); | |
159 | ||
160 | g = group_record_new(); | |
161 | if (!g) | |
162 | return -ENOMEM; | |
163 | ||
164 | r = group_record_synthesize(g, h->record); | |
165 | if (r < 0) | |
166 | return r; | |
167 | ||
168 | assert(!FLAGS_SET(g->mask, USER_RECORD_SECRET)); | |
169 | assert(!FLAGS_SET(g->mask, USER_RECORD_PRIVILEGED)); | |
170 | ||
171 | return json_build(ret, | |
172 | JSON_BUILD_OBJECT( | |
173 | JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(g->json)))); | |
174 | } | |
175 | ||
176 | static bool home_group_match_lookup_parameters(LookupParameters *p, Home *h) { | |
177 | assert(p); | |
178 | assert(h); | |
179 | ||
180 | if (p->group_name && !streq(h->user_name, p->group_name)) | |
181 | return false; | |
182 | ||
183 | if (gid_is_valid(p->gid) && h->uid != (uid_t) p->gid) | |
184 | return false; | |
185 | ||
186 | return true; | |
187 | } | |
188 | ||
189 | int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
190 | ||
191 | static const JsonDispatch dispatch_table[] = { | |
192 | { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 }, | |
193 | { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), JSON_SAFE }, | |
194 | { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, | |
195 | {} | |
196 | }; | |
197 | ||
198 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; | |
199 | LookupParameters p = { | |
200 | .gid = GID_INVALID, | |
201 | }; | |
202 | Manager *m = userdata; | |
203 | Home *h; | |
204 | int r; | |
205 | ||
206 | assert(parameters); | |
207 | assert(m); | |
208 | ||
209 | r = json_dispatch(parameters, dispatch_table, NULL, 0, &p); | |
210 | if (r < 0) | |
211 | return r; | |
212 | ||
213 | if (!streq_ptr(p.service, "io.systemd.Home")) | |
214 | return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); | |
215 | ||
216 | if (gid_is_valid(p.gid)) | |
217 | h = hashmap_get(m->homes_by_uid, UID_TO_PTR((uid_t) p.gid)); | |
218 | else if (p.group_name) | |
219 | h = hashmap_get(m->homes_by_name, p.group_name); | |
220 | else { | |
70a5db58 | 221 | |
90e74a66 | 222 | HASHMAP_FOREACH(h, m->homes_by_name) { |
70a5db58 LP |
223 | |
224 | if (!home_group_match_lookup_parameters(&p, h)) | |
225 | continue; | |
226 | ||
227 | if (v) { | |
228 | r = varlink_notify(link, v); | |
229 | if (r < 0) | |
230 | return r; | |
231 | ||
232 | v = json_variant_unref(v); | |
233 | } | |
234 | ||
235 | r = build_group_json(h, &v); | |
236 | if (r < 0) | |
237 | return r; | |
238 | } | |
239 | ||
240 | if (!v) | |
241 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
242 | ||
243 | return varlink_reply(link, v); | |
244 | } | |
245 | ||
246 | if (!h) | |
247 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
248 | ||
249 | if (!home_group_match_lookup_parameters(&p, h)) | |
250 | return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); | |
251 | ||
252 | r = build_group_json(h, &v); | |
253 | if (r < 0) | |
254 | return r; | |
255 | ||
256 | return varlink_reply(link, v); | |
257 | } | |
258 | ||
259 | int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
260 | ||
261 | static const JsonDispatch dispatch_table[] = { | |
262 | { "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), JSON_SAFE }, | |
263 | { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), JSON_SAFE }, | |
264 | { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, | |
265 | {} | |
266 | }; | |
267 | ||
268 | Manager *m = userdata; | |
269 | LookupParameters p = {}; | |
270 | Home *h; | |
271 | int r; | |
272 | ||
273 | assert(parameters); | |
274 | assert(m); | |
275 | ||
276 | r = json_dispatch(parameters, dispatch_table, NULL, 0, &p); | |
277 | if (r < 0) | |
278 | return r; | |
279 | ||
280 | if (!streq_ptr(p.service, "io.systemd.Home")) | |
281 | return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); | |
282 | ||
283 | if (p.user_name) { | |
284 | const char *last = NULL; | |
285 | char **i; | |
286 | ||
287 | h = hashmap_get(m->homes_by_name, p.user_name); | |
288 | if (!h) | |
289 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
290 | ||
291 | if (p.group_name) { | |
292 | if (!strv_contains(h->record->member_of, p.group_name)) | |
293 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
294 | ||
295 | return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)), | |
296 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name)))); | |
297 | } | |
298 | ||
299 | STRV_FOREACH(i, h->record->member_of) { | |
300 | if (last) { | |
301 | r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)), | |
302 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last)))); | |
303 | if (r < 0) | |
304 | return r; | |
305 | } | |
306 | ||
307 | last = *i; | |
308 | } | |
309 | ||
310 | if (last) | |
311 | return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)), | |
312 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last)))); | |
313 | ||
314 | } else if (p.group_name) { | |
315 | const char *last = NULL; | |
70a5db58 | 316 | |
90e74a66 | 317 | HASHMAP_FOREACH(h, m->homes_by_name) { |
70a5db58 LP |
318 | |
319 | if (!strv_contains(h->record->member_of, p.group_name)) | |
320 | continue; | |
321 | ||
322 | if (last) { | |
323 | r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last)), | |
324 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name)))); | |
325 | if (r < 0) | |
326 | return r; | |
327 | } | |
328 | ||
329 | last = h->user_name; | |
330 | } | |
331 | ||
332 | if (last) | |
333 | return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last)), | |
334 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name)))); | |
335 | } else { | |
336 | const char *last_user_name = NULL, *last_group_name = NULL; | |
70a5db58 | 337 | |
90e74a66 | 338 | HASHMAP_FOREACH(h, m->homes_by_name) { |
70a5db58 LP |
339 | char **j; |
340 | ||
341 | STRV_FOREACH(j, h->record->member_of) { | |
342 | ||
343 | if (last_user_name) { | |
344 | assert(last_group_name); | |
345 | ||
346 | r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)), | |
347 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name)))); | |
348 | ||
349 | if (r < 0) | |
350 | return r; | |
351 | } | |
352 | ||
353 | last_user_name = h->user_name; | |
354 | last_group_name = *j; | |
355 | } | |
356 | } | |
357 | ||
358 | if (last_user_name) { | |
359 | assert(last_group_name); | |
360 | return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)), | |
361 | JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name)))); | |
362 | } | |
363 | } | |
364 | ||
365 | return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
366 | } |