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