1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "sd-varlink.h"
8 #include "alloc-util.h"
11 #include "errno-util.h"
13 #include "group-record.h"
15 #include "json-util.h"
16 #include "main-func.h"
18 #include "string-util.h"
19 #include "time-util.h"
20 #include "user-record.h"
21 #include "user-util.h"
23 #include "varlink-io.systemd.UserDatabase.h"
24 #include "varlink-util.h"
26 #define ITERATIONS_MAX 64U
27 #define RUNTIME_MAX_USEC (5 * USEC_PER_MINUTE)
28 #define PRESSURE_SLEEP_TIME_USEC (50 * USEC_PER_MSEC)
29 #define CONNECTION_IDLE_USEC (15 * USEC_PER_SEC)
30 #define LISTEN_IDLE_USEC (90 * USEC_PER_SEC)
32 typedef struct LookupParameters
{
42 static void lookup_parameters_done(LookupParameters
*p
) {
45 userdb_match_done(&p
->match
);
48 static int add_nss_service(sd_json_variant
**v
) {
49 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*status
= NULL
, *z
= NULL
;
55 /* Patch in service field if it's missing. The assumption here is that this field is unset only for
58 if (sd_json_variant_by_key(*v
, "service"))
61 r
= sd_id128_get_machine(&mid
);
65 status
= sd_json_variant_ref(sd_json_variant_by_key(*v
, "status"));
66 z
= sd_json_variant_ref(sd_json_variant_by_key(status
, SD_ID128_TO_STRING(mid
)));
68 if (sd_json_variant_by_key(z
, "service"))
71 r
= sd_json_variant_set_field_string(&z
, "service", "io.systemd.NameServiceSwitch");
75 r
= sd_json_variant_set_field(&status
, SD_ID128_TO_STRING(mid
), z
);
79 return sd_json_variant_set_field(v
, "status", status
);
82 static int build_user_json(sd_varlink
*link
, UserRecord
*ur
, sd_json_variant
**ret
) {
83 _cleanup_(user_record_unrefp
) UserRecord
*stripped
= NULL
;
84 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*v
= NULL
;
85 UserRecordLoadFlags flags
;
93 r
= sd_varlink_get_peer_uid(link
, &peer_uid
);
95 log_debug_errno(r
, "Unable to query peer UID, ignoring: %m");
98 trusted
= peer_uid
== 0 || peer_uid
== ur
->uid
;
100 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
;
102 flags
|= USER_RECORD_ALLOW_PRIVILEGED
;
104 flags
|= USER_RECORD_STRIP_PRIVILEGED
;
106 r
= user_record_clone(ur
, flags
, &stripped
);
110 stripped
->incomplete
=
112 (FLAGS_SET(ur
->mask
, USER_RECORD_PRIVILEGED
) &&
113 !FLAGS_SET(stripped
->mask
, USER_RECORD_PRIVILEGED
));
115 v
= sd_json_variant_ref(stripped
->json
);
116 r
= add_nss_service(&v
);
120 return sd_json_buildo(
122 SD_JSON_BUILD_PAIR("record", SD_JSON_BUILD_VARIANT(v
)),
123 SD_JSON_BUILD_PAIR("incomplete", SD_JSON_BUILD_BOOLEAN(stripped
->incomplete
)));
126 static int userdb_flags_from_service(sd_varlink
*link
, const char *service
, UserDBFlags
*ret
) {
130 if (streq_ptr(service
, "io.systemd.NameServiceSwitch"))
131 *ret
= USERDB_NSS_ONLY
|USERDB_AVOID_MULTIPLEXER
;
132 else if (streq_ptr(service
, "io.systemd.DropIn"))
133 *ret
= USERDB_DROPIN_ONLY
|USERDB_AVOID_MULTIPLEXER
;
134 else if (streq_ptr(service
, "io.systemd.Multiplexer"))
135 *ret
= USERDB_AVOID_MULTIPLEXER
;
137 return sd_varlink_error(link
, "io.systemd.UserDatabase.BadService", NULL
);
142 static int vl_method_get_user_record(sd_varlink
*link
, sd_json_variant
*parameters
, sd_varlink_method_flags_t flags
, void *userdata
) {
144 static const sd_json_dispatch_field dispatch_table
[] = {
145 { "uid", _SD_JSON_VARIANT_TYPE_INVALID
, sd_json_dispatch_uid_gid
, offsetof(LookupParameters
, uid
), 0 },
146 { "userName", SD_JSON_VARIANT_STRING
, json_dispatch_const_user_group_name
, offsetof(LookupParameters
, name
), SD_JSON_RELAX
},
147 { "service", SD_JSON_VARIANT_STRING
, sd_json_dispatch_const_string
, offsetof(LookupParameters
, service
), 0 },
148 { "fuzzyNames", SD_JSON_VARIANT_ARRAY
, sd_json_dispatch_strv
, offsetof(LookupParameters
, match
.fuzzy_names
), 0 },
149 { "dispositionMask", SD_JSON_VARIANT_ARRAY
, json_dispatch_dispositions_mask
, offsetof(LookupParameters
, match
.disposition_mask
), 0 },
150 { "uidMin", _SD_JSON_VARIANT_TYPE_INVALID
, sd_json_dispatch_uid_gid
, offsetof(LookupParameters
, match
.uid_min
), 0 },
151 { "uidMax", _SD_JSON_VARIANT_TYPE_INVALID
, sd_json_dispatch_uid_gid
, offsetof(LookupParameters
, match
.uid_max
), 0 },
155 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*v
= NULL
;
156 _cleanup_(user_record_unrefp
) UserRecord
*hr
= NULL
;
157 _cleanup_(lookup_parameters_done
) LookupParameters p
= {
159 .match
= USERDB_MATCH_NULL
,
161 UserDBFlags userdb_flags
;
166 r
= sd_varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
170 r
= userdb_flags_from_service(link
, p
.service
, &userdb_flags
);
171 if (r
!= 0) /* return value of < 0 means error (as usual); > 0 means 'already processed and replied,
172 * we are done'; == 0 means 'not processed, caller should process now' */
175 if (uid_is_valid(p
.uid
))
176 r
= userdb_by_uid(p
.uid
, &p
.match
, userdb_flags
, &hr
);
178 r
= userdb_by_name(p
.name
, &p
.match
, userdb_flags
, &hr
);
180 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
181 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*last
= NULL
;
183 r
= userdb_all(&p
.match
, userdb_flags
, &iterator
);
184 if (IN_SET(r
, -ESRCH
, -ENOLINK
))
185 /* We turn off Varlink lookups in various cases (e.g. in case we only enable DropIn
186 * backend) — this might make userdb_all return ENOLINK (which indicates that varlink
187 * was off and no other suitable source or entries were found). Let's hide this
188 * implementation detail and always return NoRecordFound in this case, since from a
189 * client's perspective it's irrelevant if there was no entry at all or just not on
190 * the service that the query was limited to. */
191 return sd_varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
196 _cleanup_(user_record_unrefp
) UserRecord
*z
= NULL
;
198 r
= userdb_iterator_get(iterator
, &p
.match
, &z
);
205 r
= sd_varlink_notify(link
, last
);
209 last
= sd_json_variant_unref(last
);
212 r
= build_user_json(link
, z
, &last
);
218 return sd_varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
220 return sd_varlink_reply(link
, last
);
223 return sd_varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
225 return sd_varlink_error(link
, "io.systemd.UserDatabase.NonMatchingRecordFound", NULL
);
227 log_debug_errno(r
, "User lookup failed abnormally: %m");
228 return sd_varlink_error(link
, "io.systemd.UserDatabase.ServiceNotAvailable", NULL
);
231 if ((uid_is_valid(p
.uid
) && hr
->uid
!= p
.uid
) ||
232 (p
.name
&& !user_record_matches_user_name(hr
, p
.name
)))
233 return sd_varlink_error(link
, "io.systemd.UserDatabase.ConflictingRecordFound", NULL
);
235 r
= build_user_json(link
, hr
, &v
);
239 return sd_varlink_reply(link
, v
);
242 static int build_group_json(sd_varlink
*link
, GroupRecord
*gr
, sd_json_variant
**ret
) {
243 _cleanup_(group_record_unrefp
) GroupRecord
*stripped
= NULL
;
244 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*v
= NULL
;
245 UserRecordLoadFlags flags
;
253 r
= sd_varlink_get_peer_uid(link
, &peer_uid
);
255 log_debug_errno(r
, "Unable to query peer UID, ignoring: %m");
258 trusted
= peer_uid
== 0;
260 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
;
262 flags
|= USER_RECORD_ALLOW_PRIVILEGED
;
264 flags
|= USER_RECORD_STRIP_PRIVILEGED
;
266 r
= group_record_clone(gr
, flags
, &stripped
);
270 stripped
->incomplete
=
272 (FLAGS_SET(gr
->mask
, USER_RECORD_PRIVILEGED
) &&
273 !FLAGS_SET(stripped
->mask
, USER_RECORD_PRIVILEGED
));
275 v
= sd_json_variant_ref(gr
->json
);
276 r
= add_nss_service(&v
);
280 return sd_json_buildo(
282 SD_JSON_BUILD_PAIR("record", SD_JSON_BUILD_VARIANT(v
)),
283 SD_JSON_BUILD_PAIR("incomplete", SD_JSON_BUILD_BOOLEAN(stripped
->incomplete
)));
286 static int vl_method_get_group_record(sd_varlink
*link
, sd_json_variant
*parameters
, sd_varlink_method_flags_t flags
, void *userdata
) {
288 static const sd_json_dispatch_field dispatch_table
[] = {
289 { "gid", _SD_JSON_VARIANT_TYPE_INVALID
, sd_json_dispatch_uid_gid
, offsetof(LookupParameters
, gid
), 0 },
290 { "groupName", SD_JSON_VARIANT_STRING
, json_dispatch_const_user_group_name
, offsetof(LookupParameters
, name
), SD_JSON_RELAX
},
291 { "service", SD_JSON_VARIANT_STRING
, sd_json_dispatch_const_string
, offsetof(LookupParameters
, service
), 0 },
292 { "fuzzyNames", SD_JSON_VARIANT_ARRAY
, sd_json_dispatch_strv
, offsetof(LookupParameters
, match
.fuzzy_names
), 0 },
293 { "dispositionMask", SD_JSON_VARIANT_ARRAY
, json_dispatch_dispositions_mask
, offsetof(LookupParameters
, match
.disposition_mask
), 0 },
294 { "gidMin", _SD_JSON_VARIANT_TYPE_INVALID
, sd_json_dispatch_uid_gid
, offsetof(LookupParameters
, match
.gid_min
), 0 },
295 { "gidMax", _SD_JSON_VARIANT_TYPE_INVALID
, sd_json_dispatch_uid_gid
, offsetof(LookupParameters
, match
.gid_max
), 0 },
299 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*v
= NULL
;
300 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
301 _cleanup_(lookup_parameters_done
) LookupParameters p
= {
303 .match
= USERDB_MATCH_NULL
,
305 UserDBFlags userdb_flags
;
310 r
= sd_varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
314 r
= userdb_flags_from_service(link
, p
.service
, &userdb_flags
);
318 if (gid_is_valid(p
.gid
))
319 r
= groupdb_by_gid(p
.gid
, &p
.match
, userdb_flags
, &g
);
321 r
= groupdb_by_name(p
.name
, &p
.match
, userdb_flags
, &g
);
323 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
324 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*last
= NULL
;
326 r
= groupdb_all(&p
.match
, userdb_flags
, &iterator
);
327 if (IN_SET(r
, -ESRCH
, -ENOLINK
))
328 return sd_varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
333 _cleanup_(group_record_unrefp
) GroupRecord
*z
= NULL
;
335 r
= groupdb_iterator_get(iterator
, &p
.match
, &z
);
342 r
= sd_varlink_notify(link
, last
);
346 last
= sd_json_variant_unref(last
);
349 r
= build_group_json(link
, z
, &last
);
355 return sd_varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
357 return sd_varlink_reply(link
, last
);
360 return sd_varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
362 return sd_varlink_error(link
, "io.systemd.UserDatabase.NonMatchingRecordFound", NULL
);
364 log_debug_errno(r
, "Group lookup failed abnormally: %m");
365 return sd_varlink_error(link
, "io.systemd.UserDatabase.ServiceNotAvailable", NULL
);
368 if ((uid_is_valid(p
.gid
) && g
->gid
!= p
.gid
) ||
369 (p
.name
&& !group_record_matches_group_name(g
, p
.name
)))
370 return sd_varlink_error(link
, "io.systemd.UserDatabase.ConflictingRecordFound", NULL
);
372 r
= build_group_json(link
, g
, &v
);
376 return sd_varlink_reply(link
, v
);
379 typedef struct MembershipLookupParameters
{
380 const char *user_name
;
381 const char *group_name
;
383 } MembershipLookupParameters
;
385 static int vl_method_get_memberships(sd_varlink
*link
, sd_json_variant
*parameters
, sd_varlink_method_flags_t flags
, void *userdata
) {
386 static const sd_json_dispatch_field dispatch_table
[] = {
387 { "userName", SD_JSON_VARIANT_STRING
, json_dispatch_const_user_group_name
, offsetof(MembershipLookupParameters
, user_name
), SD_JSON_RELAX
},
388 { "groupName", SD_JSON_VARIANT_STRING
, json_dispatch_const_user_group_name
, offsetof(MembershipLookupParameters
, group_name
), SD_JSON_RELAX
},
389 { "service", SD_JSON_VARIANT_STRING
, sd_json_dispatch_const_string
, offsetof(MembershipLookupParameters
, service
), 0 },
393 _cleanup_free_
char *last_user_name
= NULL
, *last_group_name
= NULL
;
394 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
395 MembershipLookupParameters p
= {};
396 UserDBFlags userdb_flags
;
401 r
= sd_varlink_dispatch(link
, parameters
, dispatch_table
, &p
);
405 r
= userdb_flags_from_service(link
, p
.service
, &userdb_flags
);
410 r
= membershipdb_by_group(p
.group_name
, userdb_flags
, &iterator
);
411 else if (p
.user_name
)
412 r
= membershipdb_by_user(p
.user_name
, userdb_flags
, &iterator
);
414 r
= membershipdb_all(userdb_flags
, &iterator
);
415 if (IN_SET(r
, -ESRCH
, -ENOLINK
))
416 return sd_varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
421 _cleanup_free_
char *user_name
= NULL
, *group_name
= NULL
;
423 r
= membershipdb_iterator_get(iterator
, &user_name
, &group_name
);
429 /* If both group + user are specified do a-posteriori filtering */
430 if (p
.group_name
&& p
.user_name
&& !streq(group_name
, p
.group_name
))
433 if (last_user_name
) {
434 assert(last_group_name
);
436 r
= sd_varlink_notifybo(
438 SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(last_user_name
)),
439 SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(last_group_name
)));
444 free_and_replace(last_user_name
, user_name
);
445 free_and_replace(last_group_name
, group_name
);
448 if (!last_user_name
) {
449 assert(!last_group_name
);
450 return sd_varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
453 assert(last_group_name
);
455 return sd_varlink_replybo(
457 SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(last_user_name
)),
458 SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(last_group_name
)));
461 static int process_connection(sd_varlink_server
*server
, int _fd
) {
462 _cleanup_close_
int fd
= TAKE_FD(_fd
); /* always take possession */
463 _cleanup_(sd_varlink_close_unrefp
) sd_varlink
*vl
= NULL
;
469 r
= sd_varlink_server_add_connection(server
, fd
, &vl
);
471 return log_error_errno(r
, "Failed to add connection: %m");
474 vl
= sd_varlink_ref(vl
);
477 r
= sd_varlink_process(vl
);
478 if (r
== -ENOTCONN
) {
479 log_debug("Connection terminated.");
483 return log_error_errno(r
, "Failed to process connection: %m");
487 r
= sd_varlink_wait(vl
, CONNECTION_IDLE_USEC
);
489 return log_error_errno(r
, "Failed to wait for connection events: %m");
497 static int run(int argc
, char *argv
[]) {
498 usec_t start_time
, listen_idle_usec
, last_busy_usec
= USEC_INFINITY
;
499 _cleanup_(sd_varlink_server_unrefp
) sd_varlink_server
*server
= NULL
;
500 _cleanup_(pidref_done
) PidRef parent
= PIDREF_NULL
;
501 unsigned n_iterations
= 0;
506 m
= sd_listen_fds(false);
508 return log_error_errno(m
, "Failed to determine number of listening fds: %m");
510 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "No socket to listen on received.");
512 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Worker can only listen on a single socket at a time.");
514 listen_fd
= SD_LISTEN_FDS_START
;
516 r
= fd_nonblock(listen_fd
, false);
518 return log_error_errno(r
, "Failed to turn off non-blocking mode for listening socket: %m");
520 r
= varlink_server_new(&server
, 0, NULL
);
522 return log_error_errno(r
, "Failed to allocate varlink server: %m");
524 r
= sd_varlink_server_add_interface(server
, &vl_interface_io_systemd_UserDatabase
);
526 return log_error_errno(r
, "Failed to add UserDatabase interface to varlink server: %m");
528 r
= sd_varlink_server_bind_method_many(
530 "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record
,
531 "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record
,
532 "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships
);
534 return log_error_errno(r
, "Failed to bind methods: %m");
536 r
= getenv_bool("USERDB_FIXED_WORKER");
538 return log_error_errno(r
, "Failed to parse USERDB_FIXED_WORKER: %m");
539 listen_idle_usec
= r
? USEC_INFINITY
: LISTEN_IDLE_USEC
;
541 r
= userdb_block_nss_systemd(true);
543 return log_error_errno(r
, "Failed to disable userdb NSS compatibility: %m");
545 r
= pidref_set_parent(&parent
);
547 return log_error_errno(r
, "Failed to acquire pidfd of parent process: %m");
548 if (parent
.pid
== 1) /* We got reparented away from userdbd? */
549 return log_error_errno(SYNTHETIC_ERRNO(ESRCH
), "Parent already died, exiting.");
551 start_time
= now(CLOCK_MONOTONIC
);
554 _cleanup_close_
int fd
= -EBADF
;
557 /* Exit the worker in regular intervals, to flush out all memory use */
558 if (n_iterations
++ > ITERATIONS_MAX
) {
559 log_debug("Exiting worker, processed %u iterations, that's enough.", n_iterations
);
563 n
= now(CLOCK_MONOTONIC
);
564 if (n
>= usec_add(start_time
, RUNTIME_MAX_USEC
)) {
565 log_debug("Exiting worker, ran for %s, that's enough.",
566 FORMAT_TIMESPAN(usec_sub_unsigned(n
, start_time
), 0));
570 if (last_busy_usec
== USEC_INFINITY
)
572 else if (listen_idle_usec
!= USEC_INFINITY
&& n
>= usec_add(last_busy_usec
, listen_idle_usec
)) {
573 log_debug("Exiting worker, been idle for %s.",
574 FORMAT_TIMESPAN(usec_sub_unsigned(n
, last_busy_usec
), 0));
578 (void) rename_process("systemd-userwork: waiting...");
579 fd
= RET_NERRNO(accept4(listen_fd
, NULL
, NULL
, SOCK_NONBLOCK
|SOCK_CLOEXEC
));
580 (void) rename_process("systemd-userwork: processing...");
583 continue; /* The listening socket has SO_RECVTIMEO set, hence a timeout is expected
584 * after a while, let's check if it's time to exit though. */
586 continue; /* Might be that somebody attached via strace, let's just continue in that
589 return log_error_errno(fd
, "Failed to accept() from listening socket: %m");
591 if (now(CLOCK_MONOTONIC
) <= usec_add(n
, PRESSURE_SLEEP_TIME_USEC
)) {
592 /* We only slept a very short time? If so, let's see if there are more sockets
593 * pending, and if so, let's ask our parent for more workers */
595 r
= fd_wait_for_event(listen_fd
, POLLIN
, 0);
597 return log_error_errno(r
, "Failed to test for POLLIN on listening socket: %m");
599 if (FLAGS_SET(r
, POLLIN
)) {
600 r
= pidref_kill(&parent
, SIGUSR2
);
602 return log_error_errno(r
, "Parent already died?");
604 return log_error_errno(r
, "Failed to send SIGUSR2 signal to parent: %m");
608 (void) process_connection(server
, TAKE_FD(fd
));
609 last_busy_usec
= USEC_INFINITY
;
615 DEFINE_MAIN_FUNCTION(run
);