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"
21 #include "varlink-io.systemd.UserDatabase.h"
23 #define ITERATIONS_MAX 64U
24 #define RUNTIME_MAX_USEC (5 * USEC_PER_MINUTE)
25 #define PRESSURE_SLEEP_TIME_USEC (50 * USEC_PER_MSEC)
26 #define CONNECTION_IDLE_USEC (15 * USEC_PER_SEC)
27 #define LISTEN_IDLE_USEC (90 * USEC_PER_SEC)
29 typedef struct LookupParameters
{
30 const char *user_name
;
31 const char *group_name
;
39 static int add_nss_service(JsonVariant
**v
) {
40 _cleanup_(json_variant_unrefp
) JsonVariant
*status
= NULL
, *z
= NULL
;
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
)));
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
, SD_ID128_TO_STRING(mid
), 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
|USER_RECORD_PERMISSIVE
;
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 userdb_flags_from_service(Varlink
*link
, const char *service
, UserDBFlags
*ret
) {
120 if (streq_ptr(service
, "io.systemd.NameServiceSwitch"))
121 *ret
= USERDB_NSS_ONLY
|USERDB_AVOID_MULTIPLEXER
;
122 else if (streq_ptr(service
, "io.systemd.DropIn"))
123 *ret
= USERDB_DROPIN_ONLY
|USERDB_AVOID_MULTIPLEXER
;
124 else if (streq_ptr(service
, "io.systemd.Multiplexer"))
125 *ret
= USERDB_AVOID_MULTIPLEXER
;
127 return varlink_error(link
, "io.systemd.UserDatabase.BadService", NULL
);
132 static int vl_method_get_user_record(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
134 static const JsonDispatch dispatch_table
[] = {
135 { "uid", JSON_VARIANT_UNSIGNED
, json_dispatch_uid_gid
, offsetof(LookupParameters
, uid
), 0 },
136 { "userName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, user_name
), 0 },
137 { "service", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, service
), 0 },
141 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
142 _cleanup_(user_record_unrefp
) UserRecord
*hr
= NULL
;
143 LookupParameters p
= {
146 UserDBFlags userdb_flags
;
151 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
155 r
= userdb_flags_from_service(link
, p
.service
, &userdb_flags
);
156 if (r
!= 0) /* return value of < 0 means error (as usual); > 0 means 'already processed and replied,
157 * we are done'; == 0 means 'not processed, caller should process now' */
160 if (uid_is_valid(p
.uid
))
161 r
= userdb_by_uid(p
.uid
, userdb_flags
, &hr
);
162 else if (p
.user_name
)
163 r
= userdb_by_name(p
.user_name
, userdb_flags
, &hr
);
165 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
166 _cleanup_(json_variant_unrefp
) JsonVariant
*last
= NULL
;
168 r
= userdb_all(userdb_flags
, &iterator
);
169 if (IN_SET(r
, -ESRCH
, -ENOLINK
))
170 /* We turn off Varlink lookups in various cases (e.g. in case we only enable DropIn
171 * backend) — this might make userdb_all return ENOLINK (which indicates that varlink
172 * was off and no other suitable source or entries were found). Let's hide this
173 * implementation detail and always return NoRecordFound in this case, since from a
174 * client's perspective it's irrelevant if there was no entry at all or just not on
175 * the service that the query was limited to. */
176 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
181 _cleanup_(user_record_unrefp
) UserRecord
*z
= NULL
;
183 r
= userdb_iterator_get(iterator
, &z
);
190 r
= varlink_notify(link
, last
);
194 last
= json_variant_unref(last
);
197 r
= build_user_json(link
, z
, &last
);
203 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
205 return varlink_reply(link
, last
);
208 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
210 log_debug_errno(r
, "User lookup failed abnormally: %m");
211 return varlink_error(link
, "io.systemd.UserDatabase.ServiceNotAvailable", NULL
);
214 if ((uid_is_valid(p
.uid
) && hr
->uid
!= p
.uid
) ||
215 (p
.user_name
&& !streq(hr
->user_name
, p
.user_name
)))
216 return varlink_error(link
, "io.systemd.UserDatabase.ConflictingRecordFound", NULL
);
218 r
= build_user_json(link
, hr
, &v
);
222 return varlink_reply(link
, v
);
225 static int build_group_json(Varlink
*link
, GroupRecord
*gr
, JsonVariant
**ret
) {
226 _cleanup_(group_record_unrefp
) GroupRecord
*stripped
= NULL
;
227 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
228 UserRecordLoadFlags flags
;
236 r
= varlink_get_peer_uid(link
, &peer_uid
);
238 log_debug_errno(r
, "Unable to query peer UID, ignoring: %m");
241 trusted
= peer_uid
== 0;
243 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
;
245 flags
|= USER_RECORD_ALLOW_PRIVILEGED
;
247 flags
|= USER_RECORD_STRIP_PRIVILEGED
;
249 r
= group_record_clone(gr
, flags
, &stripped
);
253 stripped
->incomplete
=
255 (FLAGS_SET(gr
->mask
, USER_RECORD_PRIVILEGED
) &&
256 !FLAGS_SET(stripped
->mask
, USER_RECORD_PRIVILEGED
));
258 v
= json_variant_ref(gr
->json
);
259 r
= add_nss_service(&v
);
263 return json_build(ret
, JSON_BUILD_OBJECT(
264 JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(v
)),
265 JSON_BUILD_PAIR("incomplete", JSON_BUILD_BOOLEAN(stripped
->incomplete
))));
268 static int vl_method_get_group_record(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
270 static const JsonDispatch dispatch_table
[] = {
271 { "gid", JSON_VARIANT_UNSIGNED
, json_dispatch_uid_gid
, offsetof(LookupParameters
, gid
), 0 },
272 { "groupName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, group_name
), 0 },
273 { "service", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, service
), 0 },
277 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
278 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
279 LookupParameters p
= {
282 UserDBFlags userdb_flags
;
287 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
291 r
= userdb_flags_from_service(link
, p
.service
, &userdb_flags
);
295 if (gid_is_valid(p
.gid
))
296 r
= groupdb_by_gid(p
.gid
, userdb_flags
, &g
);
297 else if (p
.group_name
)
298 r
= groupdb_by_name(p
.group_name
, userdb_flags
, &g
);
300 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
301 _cleanup_(json_variant_unrefp
) JsonVariant
*last
= NULL
;
303 r
= groupdb_all(userdb_flags
, &iterator
);
304 if (IN_SET(r
, -ESRCH
, -ENOLINK
))
305 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
310 _cleanup_(group_record_unrefp
) GroupRecord
*z
= NULL
;
312 r
= groupdb_iterator_get(iterator
, &z
);
319 r
= varlink_notify(link
, last
);
323 last
= json_variant_unref(last
);
326 r
= build_group_json(link
, z
, &last
);
332 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
334 return varlink_reply(link
, last
);
337 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
339 log_debug_errno(r
, "Group lookup failed abnormally: %m");
340 return varlink_error(link
, "io.systemd.UserDatabase.ServiceNotAvailable", NULL
);
343 if ((uid_is_valid(p
.gid
) && g
->gid
!= p
.gid
) ||
344 (p
.group_name
&& !streq(g
->group_name
, p
.group_name
)))
345 return varlink_error(link
, "io.systemd.UserDatabase.ConflictingRecordFound", NULL
);
347 r
= build_group_json(link
, g
, &v
);
351 return varlink_reply(link
, v
);
354 static int vl_method_get_memberships(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
355 static const JsonDispatch dispatch_table
[] = {
356 { "userName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, user_name
), 0 },
357 { "groupName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, group_name
), 0 },
358 { "service", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, service
), 0 },
362 _cleanup_free_
char *last_user_name
= NULL
, *last_group_name
= NULL
;
363 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
364 LookupParameters p
= {};
365 UserDBFlags userdb_flags
;
370 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
374 r
= userdb_flags_from_service(link
, p
.service
, &userdb_flags
);
379 r
= membershipdb_by_group(p
.group_name
, userdb_flags
, &iterator
);
380 else if (p
.user_name
)
381 r
= membershipdb_by_user(p
.user_name
, userdb_flags
, &iterator
);
383 r
= membershipdb_all(userdb_flags
, &iterator
);
384 if (IN_SET(r
, -ESRCH
, -ENOLINK
))
385 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
390 _cleanup_free_
char *user_name
= NULL
, *group_name
= NULL
;
392 r
= membershipdb_iterator_get(iterator
, &user_name
, &group_name
);
398 /* If both group + user are specified do a-posteriori filtering */
399 if (p
.group_name
&& p
.user_name
&& !streq(group_name
, p
.group_name
))
402 if (last_user_name
) {
403 assert(last_group_name
);
405 r
= varlink_notifyb(link
, JSON_BUILD_OBJECT(
406 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name
)),
407 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name
))));
412 free_and_replace(last_user_name
, user_name
);
413 free_and_replace(last_group_name
, group_name
);
416 if (!last_user_name
) {
417 assert(!last_group_name
);
418 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
421 assert(last_group_name
);
423 return varlink_replyb(link
, JSON_BUILD_OBJECT(
424 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name
)),
425 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name
))));
428 static int process_connection(VarlinkServer
*server
, int fd
) {
429 _cleanup_(varlink_close_unrefp
) Varlink
*vl
= NULL
;
432 r
= varlink_server_add_connection(server
, fd
, &vl
);
435 return log_error_errno(r
, "Failed to add connection: %m");
438 vl
= varlink_ref(vl
);
441 r
= varlink_process(vl
);
442 if (r
== -ENOTCONN
) {
443 log_debug("Connection terminated.");
447 return log_error_errno(r
, "Failed to process connection: %m");
451 r
= varlink_wait(vl
, CONNECTION_IDLE_USEC
);
453 return log_error_errno(r
, "Failed to wait for connection events: %m");
461 static int run(int argc
, char *argv
[]) {
462 usec_t start_time
, listen_idle_usec
, last_busy_usec
= USEC_INFINITY
;
463 _cleanup_(varlink_server_unrefp
) VarlinkServer
*server
= NULL
;
464 unsigned n_iterations
= 0;
469 m
= sd_listen_fds(false);
471 return log_error_errno(m
, "Failed to determine number of listening fds: %m");
473 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "No socket to listen on received.");
475 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Worker can only listen on a single socket at a time.");
477 listen_fd
= SD_LISTEN_FDS_START
;
479 r
= fd_nonblock(listen_fd
, false);
481 return log_error_errno(r
, "Failed to turn off non-blocking mode for listening socket: %m");
483 r
= varlink_server_new(&server
, 0);
485 return log_error_errno(r
, "Failed to allocate server: %m");
487 r
= varlink_server_add_interface(server
, &vl_interface_io_systemd_UserDatabase
);
489 return log_error_errno(r
, "Failed to add UserDatabase interface to varlink server: %m");
491 r
= varlink_server_bind_method_many(
493 "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record
,
494 "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record
,
495 "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships
);
497 return log_error_errno(r
, "Failed to bind methods: %m");
499 r
= getenv_bool("USERDB_FIXED_WORKER");
501 return log_error_errno(r
, "Failed to parse USERDB_FIXED_WORKER: %m");
502 listen_idle_usec
= r
? USEC_INFINITY
: LISTEN_IDLE_USEC
;
504 r
= userdb_block_nss_systemd(true);
506 return log_error_errno(r
, "Failed to disable userdb NSS compatibility: %m");
508 start_time
= now(CLOCK_MONOTONIC
);
511 _cleanup_close_
int fd
= -EBADF
;
514 /* Exit the worker in regular intervals, to flush out all memory use */
515 if (n_iterations
++ > ITERATIONS_MAX
) {
516 log_debug("Exiting worker, processed %u iterations, that's enough.", n_iterations
);
520 n
= now(CLOCK_MONOTONIC
);
521 if (n
>= usec_add(start_time
, RUNTIME_MAX_USEC
)) {
522 log_debug("Exiting worker, ran for %s, that's enough.",
523 FORMAT_TIMESPAN(usec_sub_unsigned(n
, start_time
), 0));
527 if (last_busy_usec
== USEC_INFINITY
)
529 else if (listen_idle_usec
!= USEC_INFINITY
&& n
>= usec_add(last_busy_usec
, listen_idle_usec
)) {
530 log_debug("Exiting worker, been idle for %s.",
531 FORMAT_TIMESPAN(usec_sub_unsigned(n
, last_busy_usec
), 0));
535 (void) rename_process("systemd-userwork: waiting...");
536 fd
= RET_NERRNO(accept4(listen_fd
, NULL
, NULL
, SOCK_NONBLOCK
|SOCK_CLOEXEC
));
537 (void) rename_process("systemd-userwork: processing...");
540 continue; /* The listening socket has SO_RECVTIMEO set, hence a timeout is expected
541 * after a while, let's check if it's time to exit though. */
543 continue; /* Might be that somebody attached via strace, let's just continue in that
546 return log_error_errno(fd
, "Failed to accept() from listening socket: %m");
548 if (now(CLOCK_MONOTONIC
) <= usec_add(n
, PRESSURE_SLEEP_TIME_USEC
)) {
549 /* We only slept a very short time? If so, let's see if there are more sockets
550 * pending, and if so, let's ask our parent for more workers */
552 r
= fd_wait_for_event(listen_fd
, POLLIN
, 0);
554 return log_error_errno(r
, "Failed to test for POLLIN on listening socket: %m");
556 if (FLAGS_SET(r
, POLLIN
)) {
561 return log_error_errno(SYNTHETIC_ERRNO(ESRCH
), "Parent already died?");
563 if (kill(parent
, SIGUSR2
) < 0)
564 return log_error_errno(errno
, "Failed to kill our own parent: %m");
568 (void) process_connection(server
, TAKE_FD(fd
));
569 last_busy_usec
= USEC_INFINITY
;
575 DEFINE_MAIN_FUNCTION(run
);