1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
10 #include "group-record.h"
12 #include "main-func.h"
13 #include "process-util.h"
15 #include "time-util.h"
16 #include "user-record-nss.h"
17 #include "user-record.h"
18 #include "user-util.h"
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)
28 typedef struct LookupParameters
{
29 const char *user_name
;
30 const char *group_name
;
38 static int add_nss_service(JsonVariant
**v
) {
39 _cleanup_(json_variant_unrefp
) JsonVariant
*status
= NULL
, *z
= NULL
;
40 char buf
[SD_ID128_STRING_MAX
];
46 /* Patch in service field if it's missing. The assumption here is that this field is unset only for
49 if (json_variant_by_key(*v
, "service"))
52 r
= sd_id128_get_machine(&mid
);
56 status
= json_variant_ref(json_variant_by_key(*v
, "status"));
57 z
= json_variant_ref(json_variant_by_key(status
, sd_id128_to_string(mid
, buf
)));
59 if (json_variant_by_key(z
, "service"))
62 r
= json_variant_set_field_string(&z
, "service", "io.systemd.NameServiceSwitch");
66 r
= json_variant_set_field(&status
, buf
, z
);
70 return json_variant_set_field(v
, "status", status
);
73 static int build_user_json(Varlink
*link
, UserRecord
*ur
, JsonVariant
**ret
) {
74 _cleanup_(user_record_unrefp
) UserRecord
*stripped
= NULL
;
75 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
76 UserRecordLoadFlags flags
;
84 r
= varlink_get_peer_uid(link
, &peer_uid
);
86 log_debug_errno(r
, "Unable to query peer UID, ignoring: %m");
89 trusted
= peer_uid
== 0 || peer_uid
== ur
->uid
;
91 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
;
93 flags
|= USER_RECORD_ALLOW_PRIVILEGED
;
95 flags
|= USER_RECORD_STRIP_PRIVILEGED
;
97 r
= user_record_clone(ur
, flags
, &stripped
);
101 stripped
->incomplete
=
103 (FLAGS_SET(ur
->mask
, USER_RECORD_PRIVILEGED
) &&
104 !FLAGS_SET(stripped
->mask
, USER_RECORD_PRIVILEGED
));
106 v
= json_variant_ref(stripped
->json
);
107 r
= add_nss_service(&v
);
111 return json_build(ret
, JSON_BUILD_OBJECT(
112 JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(v
)),
113 JSON_BUILD_PAIR("incomplete", JSON_BUILD_BOOLEAN(stripped
->incomplete
))));
116 static int vl_method_get_user_record(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
118 static const JsonDispatch dispatch_table
[] = {
119 { "uid", JSON_VARIANT_UNSIGNED
, json_dispatch_uid_gid
, offsetof(LookupParameters
, uid
), 0 },
120 { "userName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, user_name
), 0 },
121 { "service", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, service
), 0 },
125 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
126 _cleanup_(user_record_unrefp
) UserRecord
*hr
= NULL
;
127 LookupParameters p
= {
134 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
138 if (streq_ptr(p
.service
, "io.systemd.NameServiceSwitch")) {
139 if (uid_is_valid(p
.uid
))
140 r
= nss_user_record_by_uid(p
.uid
, true, &hr
);
141 else if (p
.user_name
)
142 r
= nss_user_record_by_name(p
.user_name
, true, &hr
);
144 _cleanup_(json_variant_unrefp
) JsonVariant
*last
= NULL
;
149 _cleanup_(user_record_unrefp
) UserRecord
*z
= NULL
;
150 _cleanup_free_
char *sbuf
= NULL
;
158 log_debug_errno(errno
, "Failure while iterating through NSS user database, ignoring: %m");
163 r
= nss_spwd_for_passwd(pw
, &spwd
, &sbuf
);
165 log_debug_errno(r
, "Failed to acquire shadow entry for user %s, ignoring: %m", pw
->pw_name
);
167 r
= nss_passwd_to_user_record(pw
, NULL
, &z
);
174 r
= varlink_notify(link
, last
);
180 last
= json_variant_unref(last
);
183 r
= build_user_json(link
, z
, &last
);
193 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
195 return varlink_reply(link
, last
);
198 } else if (streq_ptr(p
.service
, "io.systemd.Multiplexer")) {
200 if (uid_is_valid(p
.uid
))
201 r
= userdb_by_uid(p
.uid
, USERDB_AVOID_MULTIPLEXER
, &hr
);
202 else if (p
.user_name
)
203 r
= userdb_by_name(p
.user_name
, USERDB_AVOID_MULTIPLEXER
, &hr
);
205 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
206 _cleanup_(json_variant_unrefp
) JsonVariant
*last
= NULL
;
208 r
= userdb_all(USERDB_AVOID_MULTIPLEXER
, &iterator
);
213 _cleanup_(user_record_unrefp
) UserRecord
*z
= NULL
;
215 r
= userdb_iterator_get(iterator
, &z
);
222 r
= varlink_notify(link
, last
);
226 last
= json_variant_unref(last
);
229 r
= build_user_json(link
, z
, &last
);
235 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
237 return varlink_reply(link
, last
);
240 return varlink_error(link
, "io.systemd.UserDatabase.BadService", NULL
);
242 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
244 log_debug_errno(r
, "User lookup failed abnormally: %m");
245 return varlink_error(link
, "io.systemd.UserDatabase.ServiceNotAvailable", NULL
);
248 if ((uid_is_valid(p
.uid
) && hr
->uid
!= p
.uid
) ||
249 (p
.user_name
&& !streq(hr
->user_name
, p
.user_name
)))
250 return varlink_error(link
, "io.systemd.UserDatabase.ConflictingRecordFound", NULL
);
252 r
= build_user_json(link
, hr
, &v
);
256 return varlink_reply(link
, v
);
259 static int build_group_json(Varlink
*link
, GroupRecord
*gr
, JsonVariant
**ret
) {
260 _cleanup_(group_record_unrefp
) GroupRecord
*stripped
= NULL
;
261 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
262 UserRecordLoadFlags flags
;
270 r
= varlink_get_peer_uid(link
, &peer_uid
);
272 log_debug_errno(r
, "Unable to query peer UID, ignoring: %m");
275 trusted
= peer_uid
== 0;
277 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
;
279 flags
|= USER_RECORD_ALLOW_PRIVILEGED
;
281 flags
|= USER_RECORD_STRIP_PRIVILEGED
;
283 r
= group_record_clone(gr
, flags
, &stripped
);
287 stripped
->incomplete
=
289 (FLAGS_SET(gr
->mask
, USER_RECORD_PRIVILEGED
) &&
290 !FLAGS_SET(stripped
->mask
, USER_RECORD_PRIVILEGED
));
292 v
= json_variant_ref(gr
->json
);
293 r
= add_nss_service(&v
);
297 return json_build(ret
, JSON_BUILD_OBJECT(
298 JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(v
)),
299 JSON_BUILD_PAIR("incomplete", JSON_BUILD_BOOLEAN(stripped
->incomplete
))));
302 static int vl_method_get_group_record(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
304 static const JsonDispatch dispatch_table
[] = {
305 { "gid", JSON_VARIANT_UNSIGNED
, json_dispatch_uid_gid
, offsetof(LookupParameters
, gid
), 0 },
306 { "groupName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, group_name
), 0 },
307 { "service", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, service
), 0 },
311 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
312 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
313 LookupParameters p
= {
320 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
324 if (streq_ptr(p
.service
, "io.systemd.NameServiceSwitch")) {
326 if (gid_is_valid(p
.gid
))
327 r
= nss_group_record_by_gid(p
.gid
, true, &g
);
328 else if (p
.group_name
)
329 r
= nss_group_record_by_name(p
.group_name
, true, &g
);
331 _cleanup_(json_variant_unrefp
) JsonVariant
*last
= NULL
;
336 _cleanup_(group_record_unrefp
) GroupRecord
*z
= NULL
;
337 _cleanup_free_
char *sbuf
= NULL
;
345 log_debug_errno(errno
, "Failure while iterating through NSS group database, ignoring: %m");
350 r
= nss_sgrp_for_group(grp
, &sgrp
, &sbuf
);
352 log_debug_errno(r
, "Failed to acquire shadow entry for group %s, ignoring: %m", grp
->gr_name
);
354 r
= nss_group_to_group_record(grp
, r
>= 0 ? &sgrp
: NULL
, &z
);
361 r
= varlink_notify(link
, last
);
367 last
= json_variant_unref(last
);
370 r
= build_group_json(link
, z
, &last
);
380 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
382 return varlink_reply(link
, last
);
385 } else if (streq_ptr(p
.service
, "io.systemd.Multiplexer")) {
387 if (gid_is_valid(p
.gid
))
388 r
= groupdb_by_gid(p
.gid
, USERDB_AVOID_MULTIPLEXER
, &g
);
389 else if (p
.group_name
)
390 r
= groupdb_by_name(p
.group_name
, USERDB_AVOID_MULTIPLEXER
, &g
);
392 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
393 _cleanup_(json_variant_unrefp
) JsonVariant
*last
= NULL
;
395 r
= groupdb_all(USERDB_AVOID_MULTIPLEXER
, &iterator
);
400 _cleanup_(group_record_unrefp
) GroupRecord
*z
= NULL
;
402 r
= groupdb_iterator_get(iterator
, &z
);
409 r
= varlink_notify(link
, last
);
413 last
= json_variant_unref(last
);
416 r
= build_group_json(link
, z
, &last
);
422 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
424 return varlink_reply(link
, last
);
427 return varlink_error(link
, "io.systemd.UserDatabase.BadService", NULL
);
429 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
431 log_debug_errno(r
, "Group lookup failed abnormally: %m");
432 return varlink_error(link
, "io.systemd.UserDatabase.ServiceNotAvailable", NULL
);
435 if ((uid_is_valid(p
.gid
) && g
->gid
!= p
.gid
) ||
436 (p
.group_name
&& !streq(g
->group_name
, p
.group_name
)))
437 return varlink_error(link
, "io.systemd.UserDatabase.ConflictingRecordFound", NULL
);
439 r
= build_group_json(link
, g
, &v
);
443 return varlink_reply(link
, v
);
446 static int vl_method_get_memberships(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
447 static const JsonDispatch dispatch_table
[] = {
448 { "userName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, user_name
), 0 },
449 { "groupName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, group_name
), 0 },
450 { "service", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, service
), 0 },
454 LookupParameters p
= {};
459 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
463 if (streq_ptr(p
.service
, "io.systemd.NameServiceSwitch")) {
466 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
467 const char *last
= NULL
;
470 r
= nss_group_record_by_name(p
.group_name
, true, &g
);
472 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
476 STRV_FOREACH(i
, g
->members
) {
478 if (p
.user_name
&& !streq_ptr(p
.user_name
, *i
))
482 r
= varlink_notifyb(link
, JSON_BUILD_OBJECT(
483 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last
)),
484 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(g
->group_name
))));
493 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
495 return varlink_replyb(link
, JSON_BUILD_OBJECT(
496 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last
)),
497 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(g
->group_name
))));
499 _cleanup_free_
char *last_user_name
= NULL
, *last_group_name
= NULL
;
505 const char* two
[2], **users
, **i
;
511 log_debug_errno(errno
, "Failure while iterating through NSS group database, ignoring: %m");
517 if (!strv_contains(grp
->gr_mem
, p
.user_name
))
520 two
[0] = p
.user_name
;
525 users
= (const char**) grp
->gr_mem
;
527 STRV_FOREACH(i
, users
) {
529 if (last_user_name
) {
530 assert(last_group_name
);
532 r
= varlink_notifyb(link
, JSON_BUILD_OBJECT(
533 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name
)),
534 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name
))));
540 free(last_user_name
);
541 free(last_group_name
);
544 last_user_name
= strdup(*i
);
545 last_group_name
= strdup(grp
->gr_name
);
546 if (!last_user_name
|| !last_group_name
) {
555 if (!last_user_name
) {
556 assert(!last_group_name
);
557 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
560 assert(last_group_name
);
562 return varlink_replyb(link
, JSON_BUILD_OBJECT(
563 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name
)),
564 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name
))));
567 } else if (streq_ptr(p
.service
, "io.systemd.Multiplexer")) {
569 _cleanup_free_
char *last_user_name
= NULL
, *last_group_name
= NULL
;
570 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
573 r
= membershipdb_by_group(p
.group_name
, USERDB_AVOID_MULTIPLEXER
, &iterator
);
574 else if (p
.user_name
)
575 r
= membershipdb_by_user(p
.user_name
, USERDB_AVOID_MULTIPLEXER
, &iterator
);
577 r
= membershipdb_all(USERDB_AVOID_MULTIPLEXER
, &iterator
);
582 _cleanup_free_
char *user_name
= NULL
, *group_name
= NULL
;
584 r
= membershipdb_iterator_get(iterator
, &user_name
, &group_name
);
590 /* If both group + user are specified do a-posteriori filtering */
591 if (p
.group_name
&& p
.user_name
&& !streq(group_name
, p
.group_name
))
594 if (last_user_name
) {
595 assert(last_group_name
);
597 r
= varlink_notifyb(link
, JSON_BUILD_OBJECT(
598 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name
)),
599 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name
))));
603 free(last_user_name
);
604 free(last_group_name
);
607 last_user_name
= TAKE_PTR(user_name
);
608 last_group_name
= TAKE_PTR(group_name
);
611 if (!last_user_name
) {
612 assert(!last_group_name
);
613 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
616 assert(last_group_name
);
618 return varlink_replyb(link
, JSON_BUILD_OBJECT(
619 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name
)),
620 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name
))));
623 return varlink_error(link
, "io.systemd.UserDatabase.BadService", NULL
);
626 static int process_connection(VarlinkServer
*server
, int fd
) {
627 _cleanup_(varlink_close_unrefp
) Varlink
*vl
= NULL
;
630 r
= varlink_server_add_connection(server
, fd
, &vl
);
633 return log_error_errno(r
, "Failed to add connection: %m");
636 vl
= varlink_ref(vl
);
639 r
= varlink_process(vl
);
640 if (r
== -ENOTCONN
) {
641 log_debug("Connection terminated.");
645 return log_error_errno(r
, "Failed to process connection: %m");
649 r
= varlink_wait(vl
, CONNECTION_IDLE_USEC
);
651 return log_error_errno(r
, "Failed to wait for connection events: %m");
659 static int run(int argc
, char *argv
[]) {
660 usec_t start_time
, listen_idle_usec
, last_busy_usec
= USEC_INFINITY
;
661 _cleanup_(varlink_server_unrefp
) VarlinkServer
*server
= NULL
;
662 unsigned n_iterations
= 0;
667 m
= sd_listen_fds(false);
669 return log_error_errno(m
, "Failed to determine number of listening fds: %m");
671 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "No socket to listen on received.");
673 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Worker can only listen on a single socket at a time.");
675 listen_fd
= SD_LISTEN_FDS_START
;
677 r
= fd_nonblock(listen_fd
, false);
679 return log_error_errno(r
, "Failed to turn off non-blocking mode for listening socket: %m");
681 r
= varlink_server_new(&server
, 0);
683 return log_error_errno(r
, "Failed to allocate server: %m");
685 r
= varlink_server_bind_method_many(
687 "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record
,
688 "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record
,
689 "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships
);
691 return log_error_errno(r
, "Failed to bind methods: %m");
693 r
= getenv_bool("USERDB_FIXED_WORKER");
695 return log_error_errno(r
, "Failed to parse USERDB_FIXED_WORKER: %m");
696 listen_idle_usec
= r
? USEC_INFINITY
: LISTEN_IDLE_USEC
;
698 r
= userdb_block_nss_systemd(true);
700 return log_error_errno(r
, "Failed to disable userdb NSS compatibility: %m");
702 start_time
= now(CLOCK_MONOTONIC
);
705 _cleanup_close_
int fd
= -1;
708 /* Exit the worker in regular intervals, to flush out all memory use */
709 if (n_iterations
++ > ITERATIONS_MAX
) {
710 log_debug("Exiting worker, processed %u iterations, that's enough.", n_iterations
);
714 n
= now(CLOCK_MONOTONIC
);
715 if (n
>= usec_add(start_time
, RUNTIME_MAX_USEC
)) {
716 char buf
[FORMAT_TIMESPAN_MAX
];
717 log_debug("Exiting worker, ran for %s, that's enough.",
718 format_timespan(buf
, sizeof(buf
), usec_sub_unsigned(n
, start_time
), 0));
722 if (last_busy_usec
== USEC_INFINITY
)
724 else if (listen_idle_usec
!= USEC_INFINITY
&& n
>= usec_add(last_busy_usec
, listen_idle_usec
)) {
725 char buf
[FORMAT_TIMESPAN_MAX
];
726 log_debug("Exiting worker, been idle for %s.",
727 format_timespan(buf
, sizeof(buf
), usec_sub_unsigned(n
, last_busy_usec
), 0));
731 (void) rename_process("systemd-userwork: waiting...");
733 fd
= accept4(listen_fd
, NULL
, NULL
, SOCK_NONBLOCK
|SOCK_CLOEXEC
);
737 (void) rename_process("systemd-userwork: processing...");
740 continue; /* The listening socket has SO_RECVTIMEO set, hence a timeout is expected
741 * after a while, let's check if it's time to exit though. */
743 continue; /* Might be that somebody attached via strace, let's just continue in that
746 return log_error_errno(fd
, "Failed to accept() from listening socket: %m");
748 if (now(CLOCK_MONOTONIC
) <= usec_add(n
, PRESSURE_SLEEP_TIME_USEC
)) {
749 /* We only slept a very short time? If so, let's see if there are more sockets
750 * pending, and if so, let's ask our parent for more workers */
752 r
= fd_wait_for_event(listen_fd
, POLLIN
, 0);
754 return log_error_errno(r
, "Failed to test for POLLIN on listening socket: %m");
756 if (FLAGS_SET(r
, POLLIN
)) {
761 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Parent already died?");
763 if (kill(parent
, SIGUSR2
) < 0)
764 return log_error_errno(errno
, "Failed to kill our own parent.");
768 (void) process_connection(server
, TAKE_FD(fd
));
769 last_busy_usec
= USEC_INFINITY
;
775 DEFINE_MAIN_FUNCTION(run
);