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
;
45 /* Patch in service field if it's missing. The assumption here is that this field is unset only for
48 if (json_variant_by_key(*v
, "service"))
51 r
= sd_id128_get_machine(&mid
);
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
)));
58 if (json_variant_by_key(z
, "service"))
61 r
= json_variant_set_field_string(&z
, "service", "io.systemd.NameServiceSwitch");
65 r
= json_variant_set_field(&status
, SD_ID128_TO_STRING(mid
), z
);
69 return json_variant_set_field(v
, "status", status
);
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
;
83 r
= varlink_get_peer_uid(link
, &peer_uid
);
85 log_debug_errno(r
, "Unable to query peer UID, ignoring: %m");
88 trusted
= peer_uid
== 0 || peer_uid
== ur
->uid
;
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
;
92 flags
|= USER_RECORD_ALLOW_PRIVILEGED
;
94 flags
|= USER_RECORD_STRIP_PRIVILEGED
;
96 r
= user_record_clone(ur
, flags
, &stripped
);
100 stripped
->incomplete
=
102 (FLAGS_SET(ur
->mask
, USER_RECORD_PRIVILEGED
) &&
103 !FLAGS_SET(stripped
->mask
, USER_RECORD_PRIVILEGED
));
105 v
= json_variant_ref(stripped
->json
);
106 r
= add_nss_service(&v
);
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
))));
115 static int userdb_flags_from_service(Varlink
*link
, const char *service
, UserDBFlags
*ret
) {
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
;
126 return varlink_error(link
, "io.systemd.UserDatabase.BadService", NULL
);
131 static int vl_method_get_user_record(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
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 },
140 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
141 _cleanup_(user_record_unrefp
) UserRecord
*hr
= NULL
;
142 LookupParameters p
= {
145 UserDBFlags userdb_flags
;
150 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
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' */
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
);
164 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
165 _cleanup_(json_variant_unrefp
) JsonVariant
*last
= NULL
;
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
);
180 _cleanup_(user_record_unrefp
) UserRecord
*z
= NULL
;
182 r
= userdb_iterator_get(iterator
, &z
);
189 r
= varlink_notify(link
, last
);
193 last
= json_variant_unref(last
);
196 r
= build_user_json(link
, z
, &last
);
202 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
204 return varlink_reply(link
, last
);
207 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
209 log_debug_errno(r
, "User lookup failed abnormally: %m");
210 return varlink_error(link
, "io.systemd.UserDatabase.ServiceNotAvailable", NULL
);
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
);
217 r
= build_user_json(link
, hr
, &v
);
221 return varlink_reply(link
, v
);
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
;
235 r
= varlink_get_peer_uid(link
, &peer_uid
);
237 log_debug_errno(r
, "Unable to query peer UID, ignoring: %m");
240 trusted
= peer_uid
== 0;
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
;
244 flags
|= USER_RECORD_ALLOW_PRIVILEGED
;
246 flags
|= USER_RECORD_STRIP_PRIVILEGED
;
248 r
= group_record_clone(gr
, flags
, &stripped
);
252 stripped
->incomplete
=
254 (FLAGS_SET(gr
->mask
, USER_RECORD_PRIVILEGED
) &&
255 !FLAGS_SET(stripped
->mask
, USER_RECORD_PRIVILEGED
));
257 v
= json_variant_ref(gr
->json
);
258 r
= add_nss_service(&v
);
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
))));
267 static int vl_method_get_group_record(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
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 },
276 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
277 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
278 LookupParameters p
= {
281 UserDBFlags userdb_flags
;
286 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
290 r
= userdb_flags_from_service(link
, p
.service
, &userdb_flags
);
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
);
299 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
300 _cleanup_(json_variant_unrefp
) JsonVariant
*last
= NULL
;
302 r
= groupdb_all(userdb_flags
, &iterator
);
303 if (IN_SET(r
, -ESRCH
, -ENOLINK
))
304 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
309 _cleanup_(group_record_unrefp
) GroupRecord
*z
= NULL
;
311 r
= groupdb_iterator_get(iterator
, &z
);
318 r
= varlink_notify(link
, last
);
322 last
= json_variant_unref(last
);
325 r
= build_group_json(link
, z
, &last
);
331 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
333 return varlink_reply(link
, last
);
336 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
338 log_debug_errno(r
, "Group lookup failed abnormally: %m");
339 return varlink_error(link
, "io.systemd.UserDatabase.ServiceNotAvailable", NULL
);
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
);
346 r
= build_group_json(link
, g
, &v
);
350 return varlink_reply(link
, v
);
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 },
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
;
369 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
373 r
= userdb_flags_from_service(link
, p
.service
, &userdb_flags
);
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
);
382 r
= membershipdb_all(userdb_flags
, &iterator
);
383 if (IN_SET(r
, -ESRCH
, -ENOLINK
))
384 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
389 _cleanup_free_
char *user_name
= NULL
, *group_name
= NULL
;
391 r
= membershipdb_iterator_get(iterator
, &user_name
, &group_name
);
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
))
401 if (last_user_name
) {
402 assert(last_group_name
);
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
))));
411 free_and_replace(last_user_name
, user_name
);
412 free_and_replace(last_group_name
, group_name
);
415 if (!last_user_name
) {
416 assert(!last_group_name
);
417 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
420 assert(last_group_name
);
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
))));
427 static int process_connection(VarlinkServer
*server
, int fd
) {
428 _cleanup_(varlink_close_unrefp
) Varlink
*vl
= NULL
;
431 r
= varlink_server_add_connection(server
, fd
, &vl
);
434 return log_error_errno(r
, "Failed to add connection: %m");
437 vl
= varlink_ref(vl
);
440 r
= varlink_process(vl
);
441 if (r
== -ENOTCONN
) {
442 log_debug("Connection terminated.");
446 return log_error_errno(r
, "Failed to process connection: %m");
450 r
= varlink_wait(vl
, CONNECTION_IDLE_USEC
);
452 return log_error_errno(r
, "Failed to wait for connection events: %m");
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;
468 m
= sd_listen_fds(false);
470 return log_error_errno(m
, "Failed to determine number of listening fds: %m");
472 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "No socket to listen on received.");
474 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Worker can only listen on a single socket at a time.");
476 listen_fd
= SD_LISTEN_FDS_START
;
478 r
= fd_nonblock(listen_fd
, false);
480 return log_error_errno(r
, "Failed to turn off non-blocking mode for listening socket: %m");
482 r
= varlink_server_new(&server
, 0);
484 return log_error_errno(r
, "Failed to allocate server: %m");
486 r
= varlink_server_bind_method_many(
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
);
492 return log_error_errno(r
, "Failed to bind methods: %m");
494 r
= getenv_bool("USERDB_FIXED_WORKER");
496 return log_error_errno(r
, "Failed to parse USERDB_FIXED_WORKER: %m");
497 listen_idle_usec
= r
? USEC_INFINITY
: LISTEN_IDLE_USEC
;
499 r
= userdb_block_nss_systemd(true);
501 return log_error_errno(r
, "Failed to disable userdb NSS compatibility: %m");
503 start_time
= now(CLOCK_MONOTONIC
);
506 _cleanup_close_
int fd
= -EBADF
;
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
);
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));
522 if (last_busy_usec
== USEC_INFINITY
)
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));
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...");
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. */
538 continue; /* Might be that somebody attached via strace, let's just continue in that
541 return log_error_errno(fd
, "Failed to accept() from listening socket: %m");
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 */
547 r
= fd_wait_for_event(listen_fd
, POLLIN
, 0);
549 return log_error_errno(r
, "Failed to test for POLLIN on listening socket: %m");
551 if (FLAGS_SET(r
, POLLIN
)) {
556 return log_error_errno(SYNTHETIC_ERRNO(ESRCH
), "Parent already died?");
558 if (kill(parent
, SIGUSR2
) < 0)
559 return log_error_errno(errno
, "Failed to kill our own parent: %m");
563 (void) process_connection(server
, TAKE_FD(fd
));
564 last_busy_usec
= USEC_INFINITY
;
570 DEFINE_MAIN_FUNCTION(run
);