]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
70a5db58 LP |
2 | |
3 | #include <linux/capability.h> | |
4 | ||
5 | #include "alloc-util.h" | |
6 | #include "bus-common-errors.h" | |
7 | #include "bus-polkit.h" | |
8 | #include "format-util.h" | |
d94c7eef | 9 | #include "home-util.h" |
70a5db58 LP |
10 | #include "homed-bus.h" |
11 | #include "homed-home-bus.h" | |
12 | #include "homed-manager-bus.h" | |
13 | #include "homed-manager.h" | |
14 | #include "strv.h" | |
15 | #include "user-record-sign.h" | |
16 | #include "user-record-util.h" | |
17 | #include "user-util.h" | |
18 | ||
19 | static int property_get_auto_login( | |
20 | sd_bus *bus, | |
21 | const char *path, | |
22 | const char *interface, | |
23 | const char *property, | |
24 | sd_bus_message *reply, | |
25 | void *userdata, | |
26 | sd_bus_error *error) { | |
27 | ||
99534007 | 28 | Manager *m = ASSERT_PTR(userdata); |
70a5db58 LP |
29 | Home *h; |
30 | int r; | |
31 | ||
32 | assert(bus); | |
33 | assert(reply); | |
70a5db58 LP |
34 | |
35 | r = sd_bus_message_open_container(reply, 'a', "(sso)"); | |
36 | if (r < 0) | |
37 | return r; | |
38 | ||
90e74a66 | 39 | HASHMAP_FOREACH(h, m->homes_by_name) { |
5d2a48da | 40 | _cleanup_strv_free_ char **seats = NULL; |
70a5db58 | 41 | _cleanup_free_ char *home_path = NULL; |
70a5db58 LP |
42 | |
43 | r = home_auto_login(h, &seats); | |
44 | if (r < 0) { | |
45 | log_debug_errno(r, "Failed to determine whether home '%s' is candidate for auto-login, ignoring: %m", h->user_name); | |
46 | continue; | |
47 | } | |
48 | if (!r) | |
49 | continue; | |
50 | ||
51 | r = bus_home_path(h, &home_path); | |
52 | if (r < 0) | |
53 | return log_error_errno(r, "Failed to generate home bus path: %m"); | |
54 | ||
55 | STRV_FOREACH(s, seats) { | |
56 | r = sd_bus_message_append(reply, "(sso)", h->user_name, *s, home_path); | |
57 | if (r < 0) | |
58 | return r; | |
59 | } | |
60 | } | |
61 | ||
62 | return sd_bus_message_close_container(reply); | |
63 | } | |
64 | ||
0e80e355 LP |
65 | static int lookup_user_name( |
66 | Manager *m, | |
67 | sd_bus_message *message, | |
68 | const char *user_name, | |
69 | sd_bus_error *error, | |
70 | Home **ret) { | |
71 | ||
72 | Home *h; | |
73 | int r; | |
74 | ||
75 | assert(m); | |
76 | assert(message); | |
77 | assert(user_name); | |
78 | assert(ret); | |
79 | ||
80 | if (isempty(user_name)) { | |
81 | _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; | |
82 | uid_t uid; | |
83 | ||
84 | /* If an empty user name is specified, then identify caller's EUID and find home by that. */ | |
85 | ||
86 | r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); | |
87 | if (r < 0) | |
88 | return r; | |
89 | ||
90 | r = sd_bus_creds_get_euid(creds, &uid); | |
91 | if (r < 0) | |
92 | return r; | |
93 | ||
94 | h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid)); | |
95 | if (!h) | |
96 | return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "Client's UID " UID_FMT " not managed.", uid); | |
97 | ||
98 | } else { | |
99 | ||
100 | if (!valid_user_group_name(user_name, 0)) | |
101 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name); | |
102 | ||
103 | h = hashmap_get(m->homes_by_name, user_name); | |
104 | if (!h) | |
105 | return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name); | |
106 | } | |
107 | ||
108 | *ret = h; | |
109 | return 0; | |
110 | } | |
111 | ||
70a5db58 LP |
112 | static int method_get_home_by_name( |
113 | sd_bus_message *message, | |
114 | void *userdata, | |
115 | sd_bus_error *error) { | |
116 | ||
117 | _cleanup_free_ char *path = NULL; | |
118 | const char *user_name; | |
99534007 | 119 | Manager *m = ASSERT_PTR(userdata); |
70a5db58 LP |
120 | Home *h; |
121 | int r; | |
122 | ||
123 | assert(message); | |
70a5db58 LP |
124 | |
125 | r = sd_bus_message_read(message, "s", &user_name); | |
126 | if (r < 0) | |
127 | return r; | |
70a5db58 | 128 | |
0e80e355 LP |
129 | r = lookup_user_name(m, message, user_name, error, &h); |
130 | if (r < 0) | |
131 | return r; | |
70a5db58 LP |
132 | |
133 | r = bus_home_path(h, &path); | |
134 | if (r < 0) | |
135 | return r; | |
136 | ||
137 | return sd_bus_reply_method_return( | |
138 | message, "usussso", | |
139 | (uint32_t) h->uid, | |
140 | home_state_to_string(home_get_state(h)), | |
141 | h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID, | |
142 | h->record ? user_record_real_name(h->record) : NULL, | |
143 | h->record ? user_record_home_directory(h->record) : NULL, | |
144 | h->record ? user_record_shell(h->record) : NULL, | |
145 | path); | |
146 | } | |
147 | ||
148 | static int method_get_home_by_uid( | |
149 | sd_bus_message *message, | |
150 | void *userdata, | |
151 | sd_bus_error *error) { | |
152 | ||
153 | _cleanup_free_ char *path = NULL; | |
99534007 | 154 | Manager *m = ASSERT_PTR(userdata); |
70a5db58 LP |
155 | uint32_t uid; |
156 | int r; | |
157 | Home *h; | |
158 | ||
159 | assert(message); | |
70a5db58 LP |
160 | |
161 | r = sd_bus_message_read(message, "u", &uid); | |
162 | if (r < 0) | |
163 | return r; | |
164 | if (!uid_is_valid(uid)) | |
165 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "UID " UID_FMT " is not valid", uid); | |
166 | ||
167 | h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid)); | |
168 | if (!h) | |
169 | return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for UID " UID_FMT " known", uid); | |
170 | ||
171 | /* Note that we don't use bus_home_path() here, but build the path manually, since if we are queried | |
172 | * for a UID we should also generate the bus path with a UID, and bus_home_path() uses our more | |
173 | * typical bus path by name. */ | |
174 | if (asprintf(&path, "/org/freedesktop/home1/home/" UID_FMT, h->uid) < 0) | |
175 | return -ENOMEM; | |
176 | ||
177 | return sd_bus_reply_method_return( | |
178 | message, "ssussso", | |
179 | h->user_name, | |
180 | home_state_to_string(home_get_state(h)), | |
181 | h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID, | |
182 | h->record ? user_record_real_name(h->record) : NULL, | |
183 | h->record ? user_record_home_directory(h->record) : NULL, | |
184 | h->record ? user_record_shell(h->record) : NULL, | |
185 | path); | |
186 | } | |
187 | ||
188 | static int method_list_homes( | |
189 | sd_bus_message *message, | |
190 | void *userdata, | |
191 | sd_bus_error *error) { | |
192 | ||
193 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
99534007 | 194 | Manager *m = ASSERT_PTR(userdata); |
70a5db58 LP |
195 | Home *h; |
196 | int r; | |
197 | ||
198 | assert(message); | |
70a5db58 LP |
199 | |
200 | r = sd_bus_message_new_method_return(message, &reply); | |
201 | if (r < 0) | |
202 | return r; | |
203 | ||
204 | r = sd_bus_message_open_container(reply, 'a', "(susussso)"); | |
205 | if (r < 0) | |
206 | return r; | |
207 | ||
90e74a66 | 208 | HASHMAP_FOREACH(h, m->homes_by_uid) { |
70a5db58 LP |
209 | _cleanup_free_ char *path = NULL; |
210 | ||
211 | r = bus_home_path(h, &path); | |
212 | if (r < 0) | |
213 | return r; | |
214 | ||
215 | r = sd_bus_message_append( | |
216 | reply, "(susussso)", | |
217 | h->user_name, | |
218 | (uint32_t) h->uid, | |
219 | home_state_to_string(home_get_state(h)), | |
220 | h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID, | |
221 | h->record ? user_record_real_name(h->record) : NULL, | |
222 | h->record ? user_record_home_directory(h->record) : NULL, | |
223 | h->record ? user_record_shell(h->record) : NULL, | |
224 | path); | |
225 | if (r < 0) | |
226 | return r; | |
227 | } | |
228 | ||
229 | r = sd_bus_message_close_container(reply); | |
230 | if (r < 0) | |
231 | return r; | |
232 | ||
233 | return sd_bus_send(NULL, reply, NULL); | |
234 | } | |
235 | ||
236 | static int method_get_user_record_by_name( | |
237 | sd_bus_message *message, | |
238 | void *userdata, | |
239 | sd_bus_error *error) { | |
240 | ||
241 | _cleanup_free_ char *json = NULL, *path = NULL; | |
99534007 | 242 | Manager *m = ASSERT_PTR(userdata); |
70a5db58 LP |
243 | const char *user_name; |
244 | bool incomplete; | |
245 | Home *h; | |
246 | int r; | |
247 | ||
248 | assert(message); | |
70a5db58 LP |
249 | |
250 | r = sd_bus_message_read(message, "s", &user_name); | |
251 | if (r < 0) | |
252 | return r; | |
70a5db58 | 253 | |
0e80e355 LP |
254 | r = lookup_user_name(m, message, user_name, error, &h); |
255 | if (r < 0) | |
256 | return r; | |
70a5db58 LP |
257 | |
258 | r = bus_home_get_record_json(h, message, &json, &incomplete); | |
259 | if (r < 0) | |
260 | return r; | |
261 | ||
262 | r = bus_home_path(h, &path); | |
263 | if (r < 0) | |
264 | return r; | |
265 | ||
266 | return sd_bus_reply_method_return( | |
267 | message, "sbo", | |
268 | json, | |
269 | incomplete, | |
270 | path); | |
271 | } | |
272 | ||
273 | static int method_get_user_record_by_uid( | |
274 | sd_bus_message *message, | |
275 | void *userdata, | |
276 | sd_bus_error *error) { | |
277 | ||
278 | _cleanup_free_ char *json = NULL, *path = NULL; | |
99534007 | 279 | Manager *m = ASSERT_PTR(userdata); |
70a5db58 LP |
280 | bool incomplete; |
281 | uint32_t uid; | |
282 | Home *h; | |
283 | int r; | |
284 | ||
285 | assert(message); | |
70a5db58 LP |
286 | |
287 | r = sd_bus_message_read(message, "u", &uid); | |
288 | if (r < 0) | |
289 | return r; | |
290 | if (!uid_is_valid(uid)) | |
291 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "UID " UID_FMT " is not valid", uid); | |
292 | ||
293 | h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid)); | |
294 | if (!h) | |
295 | return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for UID " UID_FMT " known", uid); | |
296 | ||
297 | r = bus_home_get_record_json(h, message, &json, &incomplete); | |
298 | if (r < 0) | |
299 | return r; | |
300 | ||
301 | if (asprintf(&path, "/org/freedesktop/home1/home/" UID_FMT, h->uid) < 0) | |
302 | return -ENOMEM; | |
303 | ||
304 | return sd_bus_reply_method_return( | |
305 | message, "sbo", | |
306 | json, | |
307 | incomplete, | |
308 | path); | |
309 | } | |
310 | ||
311 | static int generic_home_method( | |
312 | Manager *m, | |
313 | sd_bus_message *message, | |
314 | sd_bus_message_handler_t handler, | |
315 | sd_bus_error *error) { | |
316 | ||
317 | const char *user_name; | |
318 | Home *h; | |
319 | int r; | |
320 | ||
faef9ba2 LP |
321 | assert(m); |
322 | assert(message); | |
323 | assert(handler); | |
324 | ||
70a5db58 LP |
325 | r = sd_bus_message_read(message, "s", &user_name); |
326 | if (r < 0) | |
327 | return r; | |
328 | ||
0e80e355 LP |
329 | r = lookup_user_name(m, message, user_name, error, &h); |
330 | if (r < 0) | |
331 | return r; | |
70a5db58 LP |
332 | |
333 | return handler(message, h, error); | |
334 | } | |
335 | ||
336 | static int method_activate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
337 | return generic_home_method(userdata, message, bus_home_method_activate, error); | |
338 | } | |
339 | ||
340 | static int method_deactivate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
341 | return generic_home_method(userdata, message, bus_home_method_deactivate, error); | |
342 | } | |
343 | ||
a4d72746 | 344 | static int validate_and_allocate_home(Manager *m, UserRecord *hr, Hashmap *blobs, Home **ret, sd_bus_error *error) { |
70a5db58 | 345 | _cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL; |
70a5db58 LP |
346 | bool signed_locally; |
347 | Home *other; | |
348 | int r; | |
349 | ||
350 | assert(m); | |
351 | assert(hr); | |
352 | assert(ret); | |
353 | ||
354 | r = user_record_is_supported(hr, error); | |
355 | if (r < 0) | |
356 | return r; | |
357 | ||
358 | other = hashmap_get(m->homes_by_name, hr->user_name); | |
359 | if (other) | |
360 | return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists already, refusing.", hr->user_name); | |
361 | ||
75673cd8 LP |
362 | r = getpwnam_malloc(hr->user_name, /* ret= */ NULL); |
363 | if (r >= 0) | |
70a5db58 | 364 | return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists in the NSS user database, refusing.", hr->user_name); |
75673cd8 LP |
365 | if (r != -ESRCH) |
366 | return r; | |
70a5db58 | 367 | |
75673cd8 LP |
368 | r = getgrnam_malloc(hr->user_name, /* ret= */ NULL); |
369 | if (r >= 0) | |
70a5db58 | 370 | return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s conflicts with an NSS group by the same name, refusing.", hr->user_name); |
75673cd8 LP |
371 | if (r != -ESRCH) |
372 | return r; | |
70a5db58 | 373 | |
a4d72746 AV |
374 | if (blobs) { |
375 | const char *failed = NULL; | |
376 | r = user_record_ensure_blob_manifest(hr, blobs, &failed); | |
377 | if (r == -EINVAL) | |
378 | return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest."); | |
379 | if (r < 0) | |
380 | return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed)); | |
381 | } | |
382 | ||
70a5db58 LP |
383 | r = manager_verify_user_record(m, hr); |
384 | switch (r) { | |
385 | ||
386 | case USER_RECORD_UNSIGNED: | |
387 | /* If the record is unsigned, then let's sign it with our own key */ | |
388 | r = manager_sign_user_record(m, hr, &signed_hr, error); | |
389 | if (r < 0) | |
390 | return r; | |
391 | ||
392 | hr = signed_hr; | |
393 | _fallthrough_; | |
394 | ||
395 | case USER_RECORD_SIGNED_EXCLUSIVE: | |
396 | signed_locally = true; | |
397 | break; | |
398 | ||
399 | case USER_RECORD_SIGNED: | |
400 | case USER_RECORD_FOREIGN: | |
401 | signed_locally = false; | |
402 | break; | |
403 | ||
404 | case -ENOKEY: | |
405 | return sd_bus_error_setf(error, BUS_ERROR_BAD_SIGNATURE, "Specified user record for %s is signed by a key we don't recognize, refusing.", hr->user_name); | |
406 | ||
407 | default: | |
408 | return sd_bus_error_set_errnof(error, r, "Failed to validate signature for '%s': %m", hr->user_name); | |
409 | } | |
410 | ||
411 | if (uid_is_valid(hr->uid)) { | |
75673cd8 LP |
412 | _cleanup_free_ struct passwd *pw = NULL; |
413 | _cleanup_free_ struct group *gr = NULL; | |
414 | ||
70a5db58 LP |
415 | other = hashmap_get(m->homes_by_uid, UID_TO_PTR(hr->uid)); |
416 | if (other) | |
417 | return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by home %s, refusing.", hr->uid, other->user_name); | |
418 | ||
75673cd8 LP |
419 | r = getpwuid_malloc(hr->uid, &pw); |
420 | if (r >= 0) | |
70a5db58 | 421 | return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by NSS user %s, refusing.", hr->uid, pw->pw_name); |
75673cd8 LP |
422 | if (r != -ESRCH) |
423 | return r; | |
70a5db58 | 424 | |
75673cd8 LP |
425 | r = getgrgid_malloc(hr->uid, &gr); |
426 | if (r >= 0) | |
70a5db58 | 427 | return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use as GID by NSS group %s, refusing.", hr->uid, gr->gr_name); |
75673cd8 LP |
428 | if (r != -ESRCH) |
429 | return r; | |
70a5db58 LP |
430 | } else { |
431 | r = manager_augment_record_with_uid(m, hr); | |
432 | if (r < 0) | |
433 | return sd_bus_error_set_errnof(error, r, "Failed to acquire UID for '%s': %m", hr->user_name); | |
434 | } | |
435 | ||
436 | r = home_new(m, hr, NULL, ret); | |
437 | if (r < 0) | |
438 | return r; | |
439 | ||
440 | (*ret)->signed_locally = signed_locally; | |
441 | return r; | |
442 | } | |
443 | ||
444 | static int method_register_home( | |
445 | sd_bus_message *message, | |
446 | void *userdata, | |
447 | sd_bus_error *error) { | |
448 | ||
449 | _cleanup_(user_record_unrefp) UserRecord *hr = NULL; | |
99534007 | 450 | Manager *m = ASSERT_PTR(userdata); |
fc447921 | 451 | _cleanup_(home_freep) Home *h = NULL; |
70a5db58 LP |
452 | int r; |
453 | ||
454 | assert(message); | |
70a5db58 | 455 | |
bfc0cc1a | 456 | r = bus_message_read_home_record(message, USER_RECORD_LOAD_EMBEDDED|USER_RECORD_PERMISSIVE, &hr, error); |
70a5db58 LP |
457 | if (r < 0) |
458 | return r; | |
459 | ||
460 | r = bus_verify_polkit_async( | |
461 | message, | |
70a5db58 | 462 | "org.freedesktop.home1.create-home", |
7b36fb9f | 463 | /* details= */ NULL, |
70a5db58 LP |
464 | &m->polkit_registry, |
465 | error); | |
466 | if (r < 0) | |
467 | return r; | |
468 | if (r == 0) | |
469 | return 1; /* Will call us back */ | |
470 | ||
a4d72746 | 471 | r = validate_and_allocate_home(m, hr, NULL, &h, error); |
70a5db58 LP |
472 | if (r < 0) |
473 | return r; | |
474 | ||
475 | r = home_save_record(h); | |
fc447921 | 476 | if (r < 0) |
70a5db58 | 477 | return r; |
fc447921 DT |
478 | |
479 | TAKE_PTR(h); | |
70a5db58 LP |
480 | |
481 | return sd_bus_reply_method_return(message, NULL); | |
482 | } | |
483 | ||
484 | static int method_unregister_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
485 | return generic_home_method(userdata, message, bus_home_method_unregister, error); | |
486 | } | |
487 | ||
70a5db58 | 488 | |
a4d72746 | 489 | static int method_create_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { |
70a5db58 | 490 | _cleanup_(user_record_unrefp) UserRecord *hr = NULL; |
a4d72746 AV |
491 | _cleanup_hashmap_free_ Hashmap *blobs = NULL; |
492 | uint64_t flags = 0; | |
99534007 | 493 | Manager *m = ASSERT_PTR(userdata); |
70a5db58 LP |
494 | Home *h; |
495 | int r; | |
496 | ||
497 | assert(message); | |
70a5db58 LP |
498 | |
499 | r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE, &hr, error); | |
500 | if (r < 0) | |
501 | return r; | |
502 | ||
a4d72746 AV |
503 | if (endswith(sd_bus_message_get_member(message), "Ex")) { |
504 | r = bus_message_read_blobs(message, &blobs, error); | |
505 | if (r < 0) | |
506 | return r; | |
507 | ||
508 | r = sd_bus_message_read(message, "t", &flags); | |
509 | if (r < 0) | |
510 | return r; | |
d94c7eef AV |
511 | if ((flags & ~SD_HOMED_CREATE_FLAGS_ALL) != 0) |
512 | return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided."); | |
a4d72746 AV |
513 | } |
514 | ||
70a5db58 LP |
515 | r = bus_verify_polkit_async( |
516 | message, | |
70a5db58 | 517 | "org.freedesktop.home1.create-home", |
7b36fb9f | 518 | /* details= */ NULL, |
70a5db58 LP |
519 | &m->polkit_registry, |
520 | error); | |
521 | if (r < 0) | |
522 | return r; | |
523 | if (r == 0) | |
524 | return 1; /* Will call us back */ | |
525 | ||
a4d72746 | 526 | r = validate_and_allocate_home(m, hr, blobs, &h, error); |
70a5db58 LP |
527 | if (r < 0) |
528 | return r; | |
529 | ||
a4d72746 | 530 | r = home_create(h, hr, blobs, flags, error); |
70a5db58 LP |
531 | if (r < 0) |
532 | goto fail; | |
533 | ||
534 | assert(r == 0); | |
535 | h->unregister_on_failure = true; | |
536 | assert(!h->current_operation); | |
537 | ||
538 | r = home_set_current_message(h, message); | |
539 | if (r < 0) | |
540 | return r; | |
541 | ||
d94c7eef AV |
542 | h->current_operation->call_flags = flags; |
543 | ||
70a5db58 LP |
544 | return 1; |
545 | ||
546 | fail: | |
547 | (void) home_unlink_record(h); | |
548 | h = home_free(h); | |
549 | return r; | |
550 | } | |
551 | ||
552 | static int method_realize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
553 | return generic_home_method(userdata, message, bus_home_method_realize, error); | |
554 | } | |
555 | ||
556 | static int method_remove_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
557 | return generic_home_method(userdata, message, bus_home_method_remove, error); | |
558 | } | |
559 | ||
560 | static int method_fixate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
561 | return generic_home_method(userdata, message, bus_home_method_fixate, error); | |
562 | } | |
563 | ||
564 | static int method_authenticate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
565 | return generic_home_method(userdata, message, bus_home_method_authenticate, error); | |
566 | } | |
567 | ||
568 | static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
569 | _cleanup_(user_record_unrefp) UserRecord *hr = NULL; | |
a4d72746 AV |
570 | _cleanup_hashmap_free_ Hashmap *blobs = NULL; |
571 | uint64_t flags = 0; | |
99534007 | 572 | Manager *m = ASSERT_PTR(userdata); |
70a5db58 LP |
573 | Home *h; |
574 | int r; | |
575 | ||
576 | assert(message); | |
70a5db58 | 577 | |
bfc0cc1a | 578 | r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_PERMISSIVE, &hr, error); |
70a5db58 LP |
579 | if (r < 0) |
580 | return r; | |
581 | ||
a4d72746 AV |
582 | if (endswith(sd_bus_message_get_member(message), "Ex")) { |
583 | r = bus_message_read_blobs(message, &blobs, error); | |
584 | if (r < 0) | |
585 | return r; | |
586 | ||
587 | r = sd_bus_message_read(message, "t", &flags); | |
588 | if (r < 0) | |
589 | return r; | |
590 | } | |
591 | ||
70a5db58 LP |
592 | assert(hr->user_name); |
593 | ||
594 | h = hashmap_get(m->homes_by_name, hr->user_name); | |
595 | if (!h) | |
596 | return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", hr->user_name); | |
597 | ||
7555fc91 | 598 | return bus_home_update_record(h, message, hr, blobs, flags, error); |
70a5db58 LP |
599 | } |
600 | ||
601 | static int method_resize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
602 | return generic_home_method(userdata, message, bus_home_method_resize, error); | |
603 | } | |
604 | ||
605 | static int method_change_password_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
606 | return generic_home_method(userdata, message, bus_home_method_change_password, error); | |
607 | } | |
608 | ||
609 | static int method_lock_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
610 | return generic_home_method(userdata, message, bus_home_method_lock, error); | |
611 | } | |
612 | ||
613 | static int method_unlock_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
614 | return generic_home_method(userdata, message, bus_home_method_unlock, error); | |
615 | } | |
616 | ||
617 | static int method_acquire_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
618 | return generic_home_method(userdata, message, bus_home_method_acquire, error); | |
619 | } | |
620 | ||
621 | static int method_ref_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
622 | return generic_home_method(userdata, message, bus_home_method_ref, error); | |
623 | } | |
624 | ||
625 | static int method_release_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
626 | return generic_home_method(userdata, message, bus_home_method_release, error); | |
627 | } | |
628 | ||
629 | static int method_lock_all_homes(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
630 | _cleanup_(operation_unrefp) Operation *o = NULL; | |
631 | bool waiting = false; | |
99534007 | 632 | Manager *m = ASSERT_PTR(userdata); |
70a5db58 LP |
633 | Home *h; |
634 | int r; | |
635 | ||
70a5db58 LP |
636 | /* This is called from logind when we are preparing for system suspend. We enqueue a lock operation |
637 | * for every suitable home we have and only when all of them completed we send a reply indicating | |
638 | * completion. */ | |
639 | ||
90e74a66 | 640 | HASHMAP_FOREACH(h, m->homes_by_name) { |
70a5db58 | 641 | |
64b74c86 | 642 | if (!home_shall_suspend(h)) |
70a5db58 LP |
643 | continue; |
644 | ||
645 | if (!o) { | |
646 | o = operation_new(OPERATION_LOCK_ALL, message); | |
647 | if (!o) | |
648 | return -ENOMEM; | |
649 | } | |
650 | ||
80ace4f2 | 651 | log_info("Automatically locking home of user %s.", h->user_name); |
70a5db58 LP |
652 | |
653 | r = home_schedule_operation(h, o, error); | |
654 | if (r < 0) | |
655 | return r; | |
656 | ||
657 | waiting = true; | |
658 | } | |
659 | ||
660 | if (waiting) /* At least one lock operation was enqeued, let's leave here without a reply: it will | |
d1f6e01e LP |
661 | * be sent as soon as the last of the lock operations completed. */ |
662 | return 1; | |
663 | ||
664 | return sd_bus_reply_method_return(message, NULL); | |
665 | } | |
666 | ||
667 | static int method_deactivate_all_homes(sd_bus_message *message, void *userdata, sd_bus_error *error) { | |
668 | _cleanup_(operation_unrefp) Operation *o = NULL; | |
669 | bool waiting = false; | |
99534007 | 670 | Manager *m = ASSERT_PTR(userdata); |
d1f6e01e LP |
671 | Home *h; |
672 | int r; | |
673 | ||
d1f6e01e LP |
674 | /* This is called from systemd-homed-activate.service's ExecStop= command to ensure that all home |
675 | * directories are shutdown before the system goes down. Note that we don't do this from | |
676 | * systemd-homed.service itself since we want to allow restarting of it without tearing down all home | |
677 | * directories. */ | |
678 | ||
679 | HASHMAP_FOREACH(h, m->homes_by_name) { | |
680 | ||
681 | if (!o) { | |
682 | o = operation_new(OPERATION_DEACTIVATE_ALL, message); | |
683 | if (!o) | |
684 | return -ENOMEM; | |
685 | } | |
686 | ||
687 | log_info("Automatically deactivating home of user %s.", h->user_name); | |
688 | ||
689 | r = home_schedule_operation(h, o, error); | |
690 | if (r < 0) | |
691 | return r; | |
692 | ||
693 | waiting = true; | |
694 | } | |
695 | ||
696 | if (waiting) /* At least one lock operation was enqeued, let's leave here without a reply: it will be | |
697 | * sent as soon as the last of the deactivation operations completed. */ | |
70a5db58 LP |
698 | return 1; |
699 | ||
700 | return sd_bus_reply_method_return(message, NULL); | |
701 | } | |
702 | ||
49505916 | 703 | static int method_rebalance(sd_bus_message *message, void *userdata, sd_bus_error *error) { |
99534007 | 704 | Manager *m = ASSERT_PTR(userdata); |
49505916 LP |
705 | int r; |
706 | ||
49505916 LP |
707 | r = manager_schedule_rebalance(m, /* immediately= */ true); |
708 | if (r == 0) | |
709 | return sd_bus_reply_method_errorf(message, BUS_ERROR_REBALANCE_NOT_NEEDED, "No home directories need rebalancing."); | |
710 | if (r < 0) | |
711 | return r; | |
712 | ||
713 | /* Keep a reference to this message, so that we can reply to it once we are done */ | |
714 | r = set_ensure_put(&m->rebalance_queued_method_calls, &bus_message_hash_ops, message); | |
715 | if (r < 0) | |
716 | return log_error_errno(r, "Failed to track rebalance bus message: %m"); | |
717 | ||
718 | sd_bus_message_ref(message); | |
719 | return 1; | |
720 | } | |
721 | ||
cfd508a9 | 722 | static const sd_bus_vtable manager_vtable[] = { |
70a5db58 LP |
723 | SD_BUS_VTABLE_START(0), |
724 | ||
725 | SD_BUS_PROPERTY("AutoLogin", "a(sso)", property_get_auto_login, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), | |
726 | ||
9a814166 NK |
727 | SD_BUS_METHOD_WITH_ARGS("GetHomeByName", |
728 | SD_BUS_ARGS("s", user_name), | |
729 | SD_BUS_RESULT("u", uid, | |
730 | "s", home_state, | |
731 | "u", gid, | |
732 | "s", real_name, | |
733 | "s", home_directory, | |
734 | "s", shell, | |
735 | "o", bus_path), | |
736 | method_get_home_by_name, | |
737 | SD_BUS_VTABLE_UNPRIVILEGED), | |
738 | SD_BUS_METHOD_WITH_ARGS("GetHomeByUID", | |
739 | SD_BUS_ARGS("u", uid), | |
740 | SD_BUS_RESULT("s", user_name, | |
741 | "s", home_state, | |
742 | "u", gid, | |
743 | "s", real_name, | |
744 | "s", home_directory, | |
745 | "s", shell, | |
746 | "o", bus_path), | |
747 | method_get_home_by_uid, | |
748 | SD_BUS_VTABLE_UNPRIVILEGED), | |
749 | SD_BUS_METHOD_WITH_ARGS("GetUserRecordByName", | |
750 | SD_BUS_ARGS("s", user_name), | |
751 | SD_BUS_RESULT("s", user_record, "b", incomplete, "o", bus_path), | |
752 | method_get_user_record_by_name, | |
753 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
754 | SD_BUS_METHOD_WITH_ARGS("GetUserRecordByUID", | |
755 | SD_BUS_ARGS("u", uid), | |
756 | SD_BUS_RESULT("s", user_record, "b", incomplete, "o", bus_path), | |
757 | method_get_user_record_by_uid, | |
758 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
759 | SD_BUS_METHOD_WITH_ARGS("ListHomes", | |
760 | SD_BUS_NO_ARGS, | |
761 | SD_BUS_RESULT("a(susussso)", home_areas), | |
762 | method_list_homes, | |
763 | SD_BUS_VTABLE_UNPRIVILEGED), | |
70a5db58 | 764 | |
162392b7 | 765 | /* The following methods directly execute an operation on a home area, without ref-counting, queueing |
18143cd7 | 766 | * or anything, and are accessible through homectl. */ |
9a814166 NK |
767 | SD_BUS_METHOD_WITH_ARGS("ActivateHome", |
768 | SD_BUS_ARGS("s", user_name, "s", secret), | |
769 | SD_BUS_NO_RESULT, | |
770 | method_activate_home, | |
336b1f19 LP |
771 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), |
772 | SD_BUS_METHOD_WITH_ARGS("ActivateHomeIfReferenced", | |
773 | SD_BUS_ARGS("s", user_name, "s", secret), | |
774 | SD_BUS_NO_RESULT, | |
775 | method_activate_home, | |
776 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
9a814166 NK |
777 | SD_BUS_METHOD_WITH_ARGS("DeactivateHome", |
778 | SD_BUS_ARGS("s", user_name), | |
779 | SD_BUS_NO_RESULT, | |
780 | method_deactivate_home, | |
781 | 0), | |
cfd508a9 ZJS |
782 | |
783 | /* Add the JSON record to homed, but don't create actual $HOME */ | |
9a814166 NK |
784 | SD_BUS_METHOD_WITH_ARGS("RegisterHome", |
785 | SD_BUS_ARGS("s", user_record), | |
786 | SD_BUS_NO_RESULT, | |
787 | method_register_home, | |
788 | SD_BUS_VTABLE_UNPRIVILEGED), | |
cfd508a9 ZJS |
789 | |
790 | /* Remove the JSON record from homed, but don't remove actual $HOME */ | |
9a814166 NK |
791 | SD_BUS_METHOD_WITH_ARGS("UnregisterHome", |
792 | SD_BUS_ARGS("s", user_name), | |
793 | SD_BUS_NO_RESULT, | |
794 | method_unregister_home, | |
795 | SD_BUS_VTABLE_UNPRIVILEGED), | |
cfd508a9 ZJS |
796 | |
797 | /* Add JSON record, and create $HOME for it */ | |
9a814166 NK |
798 | SD_BUS_METHOD_WITH_ARGS("CreateHome", |
799 | SD_BUS_ARGS("s", user_record), | |
800 | SD_BUS_NO_RESULT, | |
801 | method_create_home, | |
802 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
a4d72746 AV |
803 | SD_BUS_METHOD_WITH_ARGS("CreateHomeEx", |
804 | SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags), | |
805 | SD_BUS_NO_RESULT, | |
806 | method_create_home, | |
807 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
cfd508a9 ZJS |
808 | |
809 | /* Create $HOME for already registered JSON entry */ | |
9a814166 NK |
810 | SD_BUS_METHOD_WITH_ARGS("RealizeHome", |
811 | SD_BUS_ARGS("s", user_name, "s", secret), | |
812 | SD_BUS_NO_RESULT, | |
813 | method_realize_home, | |
814 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
cfd508a9 ZJS |
815 | |
816 | /* Remove the JSON record and remove $HOME */ | |
9a814166 NK |
817 | SD_BUS_METHOD_WITH_ARGS("RemoveHome", |
818 | SD_BUS_ARGS("s", user_name), | |
819 | SD_BUS_NO_RESULT, | |
820 | method_remove_home, | |
821 | SD_BUS_VTABLE_UNPRIVILEGED), | |
cfd508a9 ZJS |
822 | |
823 | /* Investigate $HOME and propagate contained JSON record into our database */ | |
9a814166 NK |
824 | SD_BUS_METHOD_WITH_ARGS("FixateHome", |
825 | SD_BUS_ARGS("s", user_name, "s", secret), | |
826 | SD_BUS_NO_RESULT, | |
827 | method_fixate_home, | |
828 | SD_BUS_VTABLE_SENSITIVE), | |
cfd508a9 ZJS |
829 | |
830 | /* Just check credentials */ | |
9a814166 NK |
831 | SD_BUS_METHOD_WITH_ARGS("AuthenticateHome", |
832 | SD_BUS_ARGS("s", user_name, "s", secret), | |
833 | SD_BUS_NO_RESULT, | |
834 | method_authenticate_home, | |
835 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
cfd508a9 ZJS |
836 | |
837 | /* Update the JSON record of existing user */ | |
9a814166 NK |
838 | SD_BUS_METHOD_WITH_ARGS("UpdateHome", |
839 | SD_BUS_ARGS("s", user_record), | |
840 | SD_BUS_NO_RESULT, | |
841 | method_update_home, | |
842 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
a4d72746 AV |
843 | SD_BUS_METHOD_WITH_ARGS("UpdateHomeEx", |
844 | SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags), | |
845 | SD_BUS_NO_RESULT, | |
846 | method_update_home, | |
847 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
9a814166 NK |
848 | |
849 | SD_BUS_METHOD_WITH_ARGS("ResizeHome", | |
850 | SD_BUS_ARGS("s", user_name, "t", size, "s", secret), | |
851 | SD_BUS_NO_RESULT, | |
852 | method_resize_home, | |
853 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
854 | ||
855 | SD_BUS_METHOD_WITH_ARGS("ChangePasswordHome", | |
856 | SD_BUS_ARGS("s", user_name, "s", new_secret, "s", old_secret), | |
857 | SD_BUS_NO_RESULT, | |
858 | method_change_password_home, | |
859 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
cfd508a9 ZJS |
860 | |
861 | /* Prepare active home for system suspend: flush out passwords, suspend access */ | |
9a814166 NK |
862 | SD_BUS_METHOD_WITH_ARGS("LockHome", |
863 | SD_BUS_ARGS("s", user_name), | |
864 | SD_BUS_NO_RESULT, | |
865 | method_lock_home, | |
866 | 0), | |
cfd508a9 ZJS |
867 | |
868 | /* Make $HOME usable after system resume again */ | |
9a814166 NK |
869 | SD_BUS_METHOD_WITH_ARGS("UnlockHome", |
870 | SD_BUS_ARGS("s", user_name, "s", secret), | |
871 | SD_BUS_NO_RESULT, | |
872 | method_unlock_home, | |
873 | SD_BUS_VTABLE_SENSITIVE), | |
cfd508a9 ZJS |
874 | |
875 | /* The following methods implement ref-counted activation, and are what the PAM module and "homectl | |
876 | * with" use. In contrast to the methods above which fail if an operation is already being executed | |
877 | * on a home directory, these ones will queue the request, and are thus more reliable. Moreover, | |
878 | * they are a bit smarter: AcquireHome() will fixate, activate, unlock, or authenticate depending on | |
879 | * the state of the home area, so that the end result is always the same (i.e. the home directory is | |
880 | * accessible), and we always validate the specified passwords. RefHome() will not authenticate, and | |
881 | * thus only works if the home area is already active. */ | |
9a814166 NK |
882 | SD_BUS_METHOD_WITH_ARGS("AcquireHome", |
883 | SD_BUS_ARGS("s", user_name, "s", secret, "b", please_suspend), | |
884 | SD_BUS_RESULT("h", send_fd), | |
885 | method_acquire_home, | |
886 | SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), | |
887 | SD_BUS_METHOD_WITH_ARGS("RefHome", | |
888 | SD_BUS_ARGS("s", user_name, "b", please_suspend), | |
889 | SD_BUS_RESULT("h", send_fd), | |
890 | method_ref_home, | |
891 | 0), | |
508d344e LP |
892 | SD_BUS_METHOD_WITH_ARGS("RefHomeUnrestricted", |
893 | SD_BUS_ARGS("s", user_name, "b", please_suspend), | |
894 | SD_BUS_RESULT("h", send_fd), | |
895 | method_ref_home, | |
896 | 0), | |
9a814166 NK |
897 | SD_BUS_METHOD_WITH_ARGS("ReleaseHome", |
898 | SD_BUS_ARGS("s", user_name), | |
899 | SD_BUS_NO_RESULT, | |
900 | method_release_home, | |
901 | 0), | |
70a5db58 LP |
902 | |
903 | /* An operation that acts on all homes that allow it */ | |
904 | SD_BUS_METHOD("LockAllHomes", NULL, NULL, method_lock_all_homes, 0), | |
d1f6e01e | 905 | SD_BUS_METHOD("DeactivateAllHomes", NULL, NULL, method_deactivate_all_homes, 0), |
49505916 | 906 | SD_BUS_METHOD("Rebalance", NULL, NULL, method_rebalance, 0), |
70a5db58 LP |
907 | |
908 | SD_BUS_VTABLE_END | |
909 | }; | |
910 | ||
cfd508a9 ZJS |
911 | const BusObjectImplementation manager_object = { |
912 | "/org/freedesktop/home1", | |
913 | "org.freedesktop.home1.Manager", | |
914 | .vtables = BUS_VTABLES(manager_vtable), | |
915 | .children = BUS_IMPLEMENTATIONS(&home_object), | |
916 | }; | |
917 | ||
70a5db58 | 918 | static int on_deferred_auto_login(sd_event_source *s, void *userdata) { |
99534007 | 919 | Manager *m = ASSERT_PTR(userdata); |
70a5db58 LP |
920 | int r; |
921 | ||
cf536638 | 922 | m->deferred_auto_login_event_source = sd_event_source_disable_unref(m->deferred_auto_login_event_source); |
70a5db58 LP |
923 | |
924 | r = sd_bus_emit_properties_changed( | |
925 | m->bus, | |
926 | "/org/freedesktop/home1", | |
927 | "org.freedesktop.home1.Manager", | |
928 | "AutoLogin", NULL); | |
929 | if (r < 0) | |
930 | log_warning_errno(r, "Failed to send AutoLogin property change event, ignoring: %m"); | |
931 | ||
932 | return 0; | |
933 | } | |
934 | ||
935 | int bus_manager_emit_auto_login_changed(Manager *m) { | |
936 | int r; | |
937 | assert(m); | |
938 | ||
939 | if (m->deferred_auto_login_event_source) | |
940 | return 0; | |
941 | ||
942 | if (!m->event) | |
943 | return 0; | |
944 | ||
945 | if (IN_SET(sd_event_get_state(m->event), SD_EVENT_FINISHED, SD_EVENT_EXITING)) | |
946 | return 0; | |
947 | ||
948 | r = sd_event_add_defer(m->event, &m->deferred_auto_login_event_source, on_deferred_auto_login, m); | |
949 | if (r < 0) | |
950 | return log_error_errno(r, "Failed to allocate auto login event source: %m"); | |
951 | ||
952 | r = sd_event_source_set_priority(m->deferred_auto_login_event_source, SD_EVENT_PRIORITY_IDLE+10); | |
953 | if (r < 0) | |
954 | log_warning_errno(r, "Failed to tweak priority of event source, ignoring: %m"); | |
955 | ||
956 | (void) sd_event_source_set_description(m->deferred_auto_login_event_source, "deferred-auto-login"); | |
957 | return 1; | |
958 | } |