]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/userdb/userwork.c
21a869df1b5ce5a068466046e6d9c978ef76317d
[thirdparty/systemd.git] / src / userdb / userwork.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <poll.h>
4 #include <sys/wait.h>
5
6 #include "sd-daemon.h"
7
8 #include "env-util.h"
9 #include "fd-util.h"
10 #include "group-record.h"
11 #include "io-util.h"
12 #include "main-func.h"
13 #include "process-util.h"
14 #include "strv.h"
15 #include "time-util.h"
16 #include "user-record-nss.h"
17 #include "user-record.h"
18 #include "user-util.h"
19 #include "userdb.h"
20 #include "varlink.h"
21
22 #define ITERATIONS_MAX 64U
23 #define RUNTIME_MAX_USEC (5 * USEC_PER_MINUTE)
24 #define PRESSURE_SLEEP_TIME_USEC (50 * USEC_PER_MSEC)
25 #define CONNECTION_IDLE_USEC (15 * USEC_PER_SEC)
26 #define LISTEN_IDLE_USEC (90 * USEC_PER_SEC)
27
28 typedef struct LookupParameters {
29 const char *user_name;
30 const char *group_name;
31 union {
32 uid_t uid;
33 gid_t gid;
34 };
35 const char *service;
36 } LookupParameters;
37
38 static int add_nss_service(JsonVariant **v) {
39 _cleanup_(json_variant_unrefp) JsonVariant *status = NULL, *z = NULL;
40 sd_id128_t mid;
41 int r;
42
43 assert(v);
44
45 /* Patch in service field if it's missing. The assumption here is that this field is unset only for
46 * NSS records */
47
48 if (json_variant_by_key(*v, "service"))
49 return 0;
50
51 r = sd_id128_get_machine(&mid);
52 if (r < 0)
53 return r;
54
55 status = json_variant_ref(json_variant_by_key(*v, "status"));
56 z = json_variant_ref(json_variant_by_key(status, SD_ID128_TO_STRING(mid)));
57
58 if (json_variant_by_key(z, "service"))
59 return 0;
60
61 r = json_variant_set_field_string(&z, "service", "io.systemd.NameServiceSwitch");
62 if (r < 0)
63 return r;
64
65 r = json_variant_set_field(&status, SD_ID128_TO_STRING(mid), z);
66 if (r < 0)
67 return r;
68
69 return json_variant_set_field(v, "status", status);
70 }
71
72 static int build_user_json(Varlink *link, UserRecord *ur, JsonVariant **ret) {
73 _cleanup_(user_record_unrefp) UserRecord *stripped = NULL;
74 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
75 UserRecordLoadFlags flags;
76 uid_t peer_uid;
77 bool trusted;
78 int r;
79
80 assert(ur);
81 assert(ret);
82
83 r = varlink_get_peer_uid(link, &peer_uid);
84 if (r < 0) {
85 log_debug_errno(r, "Unable to query peer UID, ignoring: %m");
86 trusted = false;
87 } else
88 trusted = peer_uid == 0 || peer_uid == ur->uid;
89
90 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;
91 if (trusted)
92 flags |= USER_RECORD_ALLOW_PRIVILEGED;
93 else
94 flags |= USER_RECORD_STRIP_PRIVILEGED;
95
96 r = user_record_clone(ur, flags, &stripped);
97 if (r < 0)
98 return r;
99
100 stripped->incomplete =
101 ur->incomplete ||
102 (FLAGS_SET(ur->mask, USER_RECORD_PRIVILEGED) &&
103 !FLAGS_SET(stripped->mask, USER_RECORD_PRIVILEGED));
104
105 v = json_variant_ref(stripped->json);
106 r = add_nss_service(&v);
107 if (r < 0)
108 return r;
109
110 return json_build(ret, JSON_BUILD_OBJECT(
111 JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(v)),
112 JSON_BUILD_PAIR("incomplete", JSON_BUILD_BOOLEAN(stripped->incomplete))));
113 }
114
115 static int userdb_flags_from_service(Varlink *link, const char *service, UserDBFlags *ret) {
116 assert(link);
117 assert(ret);
118
119 if (streq_ptr(service, "io.systemd.NameServiceSwitch"))
120 *ret = USERDB_NSS_ONLY|USERDB_AVOID_MULTIPLEXER;
121 else if (streq_ptr(service, "io.systemd.DropIn"))
122 *ret = USERDB_DROPIN_ONLY|USERDB_AVOID_MULTIPLEXER;
123 else if (streq_ptr(service, "io.systemd.Multiplexer"))
124 *ret = USERDB_AVOID_MULTIPLEXER;
125 else
126 return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
127
128 return 0;
129 }
130
131 static int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
132
133 static const JsonDispatch dispatch_table[] = {
134 { "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 },
135 { "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), 0 },
136 { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
137 {}
138 };
139
140 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
141 _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
142 LookupParameters p = {
143 .uid = UID_INVALID,
144 };
145 UserDBFlags userdb_flags;
146 int r;
147
148 assert(parameters);
149
150 r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
151 if (r < 0)
152 return r;
153
154 r = userdb_flags_from_service(link, p.service, &userdb_flags);
155 if (r != 0) /* return value of < 0 means error (as usual); > 0 means 'already processed and replied,
156 * we are done'; == 0 means 'not processed, caller should process now' */
157 return r;
158
159 if (uid_is_valid(p.uid))
160 r = userdb_by_uid(p.uid, userdb_flags, &hr);
161 else if (p.user_name)
162 r = userdb_by_name(p.user_name, userdb_flags, &hr);
163 else {
164 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
165 _cleanup_(json_variant_unrefp) JsonVariant *last = NULL;
166
167 r = userdb_all(userdb_flags, &iterator);
168 if (IN_SET(r, -ESRCH, -ENOLINK))
169 /* We turn off Varlink lookups in various cases (e.g. in case we only enable DropIn
170 * backend) — this might make userdb_all return ENOLINK (which indicates that varlink
171 * was off and no other suitable source or entries were found). Let's hide this
172 * implementation detail and always return NoRecordFound in this case, since from a
173 * client's perspective it's irrelevant if there was no entry at all or just not on
174 * the service that the query was limited to. */
175 return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
176 if (r < 0)
177 return r;
178
179 for (;;) {
180 _cleanup_(user_record_unrefp) UserRecord *z = NULL;
181
182 r = userdb_iterator_get(iterator, &z);
183 if (r == -ESRCH)
184 break;
185 if (r < 0)
186 return r;
187
188 if (last) {
189 r = varlink_notify(link, last);
190 if (r < 0)
191 return r;
192
193 last = json_variant_unref(last);
194 }
195
196 r = build_user_json(link, z, &last);
197 if (r < 0)
198 return r;
199 }
200
201 if (!last)
202 return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
203
204 return varlink_reply(link, last);
205 }
206 if (r == -ESRCH)
207 return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
208 if (r < 0) {
209 log_debug_errno(r, "User lookup failed abnormally: %m");
210 return varlink_error(link, "io.systemd.UserDatabase.ServiceNotAvailable", NULL);
211 }
212
213 if ((uid_is_valid(p.uid) && hr->uid != p.uid) ||
214 (p.user_name && !streq(hr->user_name, p.user_name)))
215 return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
216
217 r = build_user_json(link, hr, &v);
218 if (r < 0)
219 return r;
220
221 return varlink_reply(link, v);
222 }
223
224 static int build_group_json(Varlink *link, GroupRecord *gr, JsonVariant **ret) {
225 _cleanup_(group_record_unrefp) GroupRecord *stripped = NULL;
226 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
227 UserRecordLoadFlags flags;
228 uid_t peer_uid;
229 bool trusted;
230 int r;
231
232 assert(gr);
233 assert(ret);
234
235 r = varlink_get_peer_uid(link, &peer_uid);
236 if (r < 0) {
237 log_debug_errno(r, "Unable to query peer UID, ignoring: %m");
238 trusted = false;
239 } else
240 trusted = peer_uid == 0;
241
242 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;
243 if (trusted)
244 flags |= USER_RECORD_ALLOW_PRIVILEGED;
245 else
246 flags |= USER_RECORD_STRIP_PRIVILEGED;
247
248 r = group_record_clone(gr, flags, &stripped);
249 if (r < 0)
250 return r;
251
252 stripped->incomplete =
253 gr->incomplete ||
254 (FLAGS_SET(gr->mask, USER_RECORD_PRIVILEGED) &&
255 !FLAGS_SET(stripped->mask, USER_RECORD_PRIVILEGED));
256
257 v = json_variant_ref(gr->json);
258 r = add_nss_service(&v);
259 if (r < 0)
260 return r;
261
262 return json_build(ret, JSON_BUILD_OBJECT(
263 JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(v)),
264 JSON_BUILD_PAIR("incomplete", JSON_BUILD_BOOLEAN(stripped->incomplete))));
265 }
266
267 static int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
268
269 static const JsonDispatch dispatch_table[] = {
270 { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 },
271 { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), 0 },
272 { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
273 {}
274 };
275
276 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
277 _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
278 LookupParameters p = {
279 .gid = GID_INVALID,
280 };
281 UserDBFlags userdb_flags;
282 int r;
283
284 assert(parameters);
285
286 r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
287 if (r < 0)
288 return r;
289
290 r = userdb_flags_from_service(link, p.service, &userdb_flags);
291 if (r != 0)
292 return r;
293
294 if (gid_is_valid(p.gid))
295 r = groupdb_by_gid(p.gid, userdb_flags, &g);
296 else if (p.group_name)
297 r = groupdb_by_name(p.group_name, userdb_flags, &g);
298 else {
299 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
300 _cleanup_(json_variant_unrefp) JsonVariant *last = NULL;
301
302 r = groupdb_all(userdb_flags, &iterator);
303 if (IN_SET(r, -ESRCH, -ENOLINK))
304 return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
305 if (r < 0)
306 return r;
307
308 for (;;) {
309 _cleanup_(group_record_unrefp) GroupRecord *z = NULL;
310
311 r = groupdb_iterator_get(iterator, &z);
312 if (r == -ESRCH)
313 break;
314 if (r < 0)
315 return r;
316
317 if (last) {
318 r = varlink_notify(link, last);
319 if (r < 0)
320 return r;
321
322 last = json_variant_unref(last);
323 }
324
325 r = build_group_json(link, z, &last);
326 if (r < 0)
327 return r;
328 }
329
330 if (!last)
331 return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
332
333 return varlink_reply(link, last);
334 }
335 if (r == -ESRCH)
336 return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
337 if (r < 0) {
338 log_debug_errno(r, "Group lookup failed abnormally: %m");
339 return varlink_error(link, "io.systemd.UserDatabase.ServiceNotAvailable", NULL);
340 }
341
342 if ((uid_is_valid(p.gid) && g->gid != p.gid) ||
343 (p.group_name && !streq(g->group_name, p.group_name)))
344 return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
345
346 r = build_group_json(link, g, &v);
347 if (r < 0)
348 return r;
349
350 return varlink_reply(link, v);
351 }
352
353 static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
354 static const JsonDispatch dispatch_table[] = {
355 { "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), 0 },
356 { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), 0 },
357 { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
358 {}
359 };
360
361 _cleanup_free_ char *last_user_name = NULL, *last_group_name = NULL;
362 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
363 LookupParameters p = {};
364 UserDBFlags userdb_flags;
365 int r;
366
367 assert(parameters);
368
369 r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
370 if (r < 0)
371 return r;
372
373 r = userdb_flags_from_service(link, p.service, &userdb_flags);
374 if (r != 0)
375 return r;
376
377 if (p.group_name)
378 r = membershipdb_by_group(p.group_name, userdb_flags, &iterator);
379 else if (p.user_name)
380 r = membershipdb_by_user(p.user_name, userdb_flags, &iterator);
381 else
382 r = membershipdb_all(userdb_flags, &iterator);
383 if (IN_SET(r, -ESRCH, -ENOLINK))
384 return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
385 if (r < 0)
386 return r;
387
388 for (;;) {
389 _cleanup_free_ char *user_name = NULL, *group_name = NULL;
390
391 r = membershipdb_iterator_get(iterator, &user_name, &group_name);
392 if (r == -ESRCH)
393 break;
394 if (r < 0)
395 return r;
396
397 /* If both group + user are specified do a-posteriori filtering */
398 if (p.group_name && p.user_name && !streq(group_name, p.group_name))
399 continue;
400
401 if (last_user_name) {
402 assert(last_group_name);
403
404 r = varlink_notifyb(link, JSON_BUILD_OBJECT(
405 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)),
406 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name))));
407 if (r < 0)
408 return r;
409 }
410
411 free_and_replace(last_user_name, user_name);
412 free_and_replace(last_group_name, group_name);
413 }
414
415 if (!last_user_name) {
416 assert(!last_group_name);
417 return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
418 }
419
420 assert(last_group_name);
421
422 return varlink_replyb(link, JSON_BUILD_OBJECT(
423 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)),
424 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name))));
425 }
426
427 static int process_connection(VarlinkServer *server, int fd) {
428 _cleanup_(varlink_close_unrefp) Varlink *vl = NULL;
429 int r;
430
431 r = varlink_server_add_connection(server, fd, &vl);
432 if (r < 0) {
433 fd = safe_close(fd);
434 return log_error_errno(r, "Failed to add connection: %m");
435 }
436
437 vl = varlink_ref(vl);
438
439 for (;;) {
440 r = varlink_process(vl);
441 if (r == -ENOTCONN) {
442 log_debug("Connection terminated.");
443 break;
444 }
445 if (r < 0)
446 return log_error_errno(r, "Failed to process connection: %m");
447 if (r > 0)
448 continue;
449
450 r = varlink_wait(vl, CONNECTION_IDLE_USEC);
451 if (r < 0)
452 return log_error_errno(r, "Failed to wait for connection events: %m");
453 if (r == 0)
454 break;
455 }
456
457 return 0;
458 }
459
460 static int run(int argc, char *argv[]) {
461 usec_t start_time, listen_idle_usec, last_busy_usec = USEC_INFINITY;
462 _cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL;
463 unsigned n_iterations = 0;
464 int m, listen_fd, r;
465
466 log_setup();
467
468 m = sd_listen_fds(false);
469 if (m < 0)
470 return log_error_errno(m, "Failed to determine number of listening fds: %m");
471 if (m == 0)
472 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No socket to listen on received.");
473 if (m > 1)
474 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Worker can only listen on a single socket at a time.");
475
476 listen_fd = SD_LISTEN_FDS_START;
477
478 r = fd_nonblock(listen_fd, false);
479 if (r < 0)
480 return log_error_errno(r, "Failed to turn off non-blocking mode for listening socket: %m");
481
482 r = varlink_server_new(&server, 0);
483 if (r < 0)
484 return log_error_errno(r, "Failed to allocate server: %m");
485
486 r = varlink_server_bind_method_many(
487 server,
488 "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record,
489 "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record,
490 "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships);
491 if (r < 0)
492 return log_error_errno(r, "Failed to bind methods: %m");
493
494 r = getenv_bool("USERDB_FIXED_WORKER");
495 if (r < 0)
496 return log_error_errno(r, "Failed to parse USERDB_FIXED_WORKER: %m");
497 listen_idle_usec = r ? USEC_INFINITY : LISTEN_IDLE_USEC;
498
499 r = userdb_block_nss_systemd(true);
500 if (r < 0)
501 return log_error_errno(r, "Failed to disable userdb NSS compatibility: %m");
502
503 start_time = now(CLOCK_MONOTONIC);
504
505 for (;;) {
506 _cleanup_close_ int fd = -1;
507 usec_t n;
508
509 /* Exit the worker in regular intervals, to flush out all memory use */
510 if (n_iterations++ > ITERATIONS_MAX) {
511 log_debug("Exiting worker, processed %u iterations, that's enough.", n_iterations);
512 break;
513 }
514
515 n = now(CLOCK_MONOTONIC);
516 if (n >= usec_add(start_time, RUNTIME_MAX_USEC)) {
517 log_debug("Exiting worker, ran for %s, that's enough.",
518 FORMAT_TIMESPAN(usec_sub_unsigned(n, start_time), 0));
519 break;
520 }
521
522 if (last_busy_usec == USEC_INFINITY)
523 last_busy_usec = n;
524 else if (listen_idle_usec != USEC_INFINITY && n >= usec_add(last_busy_usec, listen_idle_usec)) {
525 log_debug("Exiting worker, been idle for %s.",
526 FORMAT_TIMESPAN(usec_sub_unsigned(n, last_busy_usec), 0));
527 break;
528 }
529
530 (void) rename_process("systemd-userwork: waiting...");
531 fd = RET_NERRNO(accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC));
532 (void) rename_process("systemd-userwork: processing...");
533
534 if (fd == -EAGAIN)
535 continue; /* The listening socket has SO_RECVTIMEO set, hence a timeout is expected
536 * after a while, let's check if it's time to exit though. */
537 if (fd == -EINTR)
538 continue; /* Might be that somebody attached via strace, let's just continue in that
539 * case */
540 if (fd < 0)
541 return log_error_errno(fd, "Failed to accept() from listening socket: %m");
542
543 if (now(CLOCK_MONOTONIC) <= usec_add(n, PRESSURE_SLEEP_TIME_USEC)) {
544 /* We only slept a very short time? If so, let's see if there are more sockets
545 * pending, and if so, let's ask our parent for more workers */
546
547 r = fd_wait_for_event(listen_fd, POLLIN, 0);
548 if (r < 0)
549 return log_error_errno(r, "Failed to test for POLLIN on listening socket: %m");
550
551 if (FLAGS_SET(r, POLLIN)) {
552 pid_t parent;
553
554 parent = getppid();
555 if (parent <= 1)
556 return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Parent already died?");
557
558 if (kill(parent, SIGUSR2) < 0)
559 return log_error_errno(errno, "Failed to kill our own parent: %m");
560 }
561 }
562
563 (void) process_connection(server, TAKE_FD(fd));
564 last_busy_usec = USEC_INFINITY;
565 }
566
567 return 0;
568 }
569
570 DEFINE_MAIN_FUNCTION(run);