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
|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
) {
121 if (streq_ptr(service
, "io.systemd.NameServiceSwitch"))
122 *ret
= USERDB_NSS_ONLY
|USERDB_AVOID_MULTIPLEXER
;
123 if (streq_ptr(service
, "io.systemd.DropIn"))
124 *ret
= USERDB_DROPIN_ONLY
|USERDB_AVOID_MULTIPLEXER
;
125 else if (streq_ptr(service
, "io.systemd.Multiplexer"))
126 *ret
= USERDB_AVOID_MULTIPLEXER
;
128 return varlink_error(link
, "io.systemd.UserDatabase.BadService", NULL
);
133 static int vl_method_get_user_record(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
135 static const JsonDispatch dispatch_table
[] = {
136 { "uid", JSON_VARIANT_UNSIGNED
, json_dispatch_uid_gid
, offsetof(LookupParameters
, uid
), 0 },
137 { "userName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, user_name
), 0 },
138 { "service", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, service
), 0 },
142 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
143 _cleanup_(user_record_unrefp
) UserRecord
*hr
= NULL
;
144 LookupParameters p
= {
147 UserDBFlags userdb_flags
;
152 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
156 r
= userdb_flags_from_service(link
, p
.service
, &userdb_flags
);
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
);
173 _cleanup_(user_record_unrefp
) UserRecord
*z
= NULL
;
175 r
= userdb_iterator_get(iterator
, &z
);
182 r
= varlink_notify(link
, last
);
186 last
= json_variant_unref(last
);
189 r
= build_user_json(link
, z
, &last
);
195 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
197 return varlink_reply(link
, last
);
200 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
202 log_debug_errno(r
, "User lookup failed abnormally: %m");
203 return varlink_error(link
, "io.systemd.UserDatabase.ServiceNotAvailable", NULL
);
206 if ((uid_is_valid(p
.uid
) && hr
->uid
!= p
.uid
) ||
207 (p
.user_name
&& !streq(hr
->user_name
, p
.user_name
)))
208 return varlink_error(link
, "io.systemd.UserDatabase.ConflictingRecordFound", NULL
);
210 r
= build_user_json(link
, hr
, &v
);
214 return varlink_reply(link
, v
);
217 static int build_group_json(Varlink
*link
, GroupRecord
*gr
, JsonVariant
**ret
) {
218 _cleanup_(group_record_unrefp
) GroupRecord
*stripped
= NULL
;
219 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
220 UserRecordLoadFlags flags
;
228 r
= varlink_get_peer_uid(link
, &peer_uid
);
230 log_debug_errno(r
, "Unable to query peer UID, ignoring: %m");
233 trusted
= peer_uid
== 0;
235 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
;
237 flags
|= USER_RECORD_ALLOW_PRIVILEGED
;
239 flags
|= USER_RECORD_STRIP_PRIVILEGED
;
241 r
= group_record_clone(gr
, flags
, &stripped
);
245 stripped
->incomplete
=
247 (FLAGS_SET(gr
->mask
, USER_RECORD_PRIVILEGED
) &&
248 !FLAGS_SET(stripped
->mask
, USER_RECORD_PRIVILEGED
));
250 v
= json_variant_ref(gr
->json
);
251 r
= add_nss_service(&v
);
255 return json_build(ret
, JSON_BUILD_OBJECT(
256 JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(v
)),
257 JSON_BUILD_PAIR("incomplete", JSON_BUILD_BOOLEAN(stripped
->incomplete
))));
260 static int vl_method_get_group_record(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
262 static const JsonDispatch dispatch_table
[] = {
263 { "gid", JSON_VARIANT_UNSIGNED
, json_dispatch_uid_gid
, offsetof(LookupParameters
, gid
), 0 },
264 { "groupName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, group_name
), 0 },
265 { "service", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, service
), 0 },
269 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
270 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
271 LookupParameters p
= {
274 UserDBFlags userdb_flags
;
279 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
283 r
= userdb_flags_from_service(link
, p
.service
, &userdb_flags
);
287 if (gid_is_valid(p
.gid
))
288 r
= groupdb_by_gid(p
.gid
, userdb_flags
, &g
);
289 else if (p
.group_name
)
290 r
= groupdb_by_name(p
.group_name
, userdb_flags
, &g
);
292 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
293 _cleanup_(json_variant_unrefp
) JsonVariant
*last
= NULL
;
295 r
= groupdb_all(userdb_flags
, &iterator
);
300 _cleanup_(group_record_unrefp
) GroupRecord
*z
= NULL
;
302 r
= groupdb_iterator_get(iterator
, &z
);
309 r
= varlink_notify(link
, last
);
313 last
= json_variant_unref(last
);
316 r
= build_group_json(link
, z
, &last
);
322 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
324 return varlink_reply(link
, last
);
327 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
329 log_debug_errno(r
, "Group lookup failed abnormally: %m");
330 return varlink_error(link
, "io.systemd.UserDatabase.ServiceNotAvailable", NULL
);
333 if ((uid_is_valid(p
.gid
) && g
->gid
!= p
.gid
) ||
334 (p
.group_name
&& !streq(g
->group_name
, p
.group_name
)))
335 return varlink_error(link
, "io.systemd.UserDatabase.ConflictingRecordFound", NULL
);
337 r
= build_group_json(link
, g
, &v
);
341 return varlink_reply(link
, v
);
344 static int vl_method_get_memberships(Varlink
*link
, JsonVariant
*parameters
, VarlinkMethodFlags flags
, void *userdata
) {
345 static const JsonDispatch dispatch_table
[] = {
346 { "userName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, user_name
), 0 },
347 { "groupName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, group_name
), 0 },
348 { "service", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(LookupParameters
, service
), 0 },
352 _cleanup_free_
char *last_user_name
= NULL
, *last_group_name
= NULL
;
353 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
354 LookupParameters p
= {};
355 UserDBFlags userdb_flags
;
360 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &p
);
364 r
= userdb_flags_from_service(link
, p
.service
, &userdb_flags
);
369 r
= membershipdb_by_group(p
.group_name
, userdb_flags
, &iterator
);
370 else if (p
.user_name
)
371 r
= membershipdb_by_user(p
.user_name
, userdb_flags
, &iterator
);
373 r
= membershipdb_all(userdb_flags
, &iterator
);
378 _cleanup_free_
char *user_name
= NULL
, *group_name
= NULL
;
380 r
= membershipdb_iterator_get(iterator
, &user_name
, &group_name
);
386 /* If both group + user are specified do a-posteriori filtering */
387 if (p
.group_name
&& p
.user_name
&& !streq(group_name
, p
.group_name
))
390 if (last_user_name
) {
391 assert(last_group_name
);
393 r
= varlink_notifyb(link
, JSON_BUILD_OBJECT(
394 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name
)),
395 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name
))));
400 free_and_replace(last_user_name
, user_name
);
401 free_and_replace(last_group_name
, group_name
);
404 if (!last_user_name
) {
405 assert(!last_group_name
);
406 return varlink_error(link
, "io.systemd.UserDatabase.NoRecordFound", NULL
);
409 assert(last_group_name
);
411 return varlink_replyb(link
, JSON_BUILD_OBJECT(
412 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name
)),
413 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name
))));
416 static int process_connection(VarlinkServer
*server
, int fd
) {
417 _cleanup_(varlink_close_unrefp
) Varlink
*vl
= NULL
;
420 r
= varlink_server_add_connection(server
, fd
, &vl
);
423 return log_error_errno(r
, "Failed to add connection: %m");
426 vl
= varlink_ref(vl
);
429 r
= varlink_process(vl
);
430 if (r
== -ENOTCONN
) {
431 log_debug("Connection terminated.");
435 return log_error_errno(r
, "Failed to process connection: %m");
439 r
= varlink_wait(vl
, CONNECTION_IDLE_USEC
);
441 return log_error_errno(r
, "Failed to wait for connection events: %m");
449 static int run(int argc
, char *argv
[]) {
450 usec_t start_time
, listen_idle_usec
, last_busy_usec
= USEC_INFINITY
;
451 _cleanup_(varlink_server_unrefp
) VarlinkServer
*server
= NULL
;
452 unsigned n_iterations
= 0;
457 m
= sd_listen_fds(false);
459 return log_error_errno(m
, "Failed to determine number of listening fds: %m");
461 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "No socket to listen on received.");
463 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Worker can only listen on a single socket at a time.");
465 listen_fd
= SD_LISTEN_FDS_START
;
467 r
= fd_nonblock(listen_fd
, false);
469 return log_error_errno(r
, "Failed to turn off non-blocking mode for listening socket: %m");
471 r
= varlink_server_new(&server
, 0);
473 return log_error_errno(r
, "Failed to allocate server: %m");
475 r
= varlink_server_bind_method_many(
477 "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record
,
478 "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record
,
479 "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships
);
481 return log_error_errno(r
, "Failed to bind methods: %m");
483 r
= getenv_bool("USERDB_FIXED_WORKER");
485 return log_error_errno(r
, "Failed to parse USERDB_FIXED_WORKER: %m");
486 listen_idle_usec
= r
? USEC_INFINITY
: LISTEN_IDLE_USEC
;
488 r
= userdb_block_nss_systemd(true);
490 return log_error_errno(r
, "Failed to disable userdb NSS compatibility: %m");
492 start_time
= now(CLOCK_MONOTONIC
);
495 _cleanup_close_
int fd
= -1;
498 /* Exit the worker in regular intervals, to flush out all memory use */
499 if (n_iterations
++ > ITERATIONS_MAX
) {
500 log_debug("Exiting worker, processed %u iterations, that's enough.", n_iterations
);
504 n
= now(CLOCK_MONOTONIC
);
505 if (n
>= usec_add(start_time
, RUNTIME_MAX_USEC
)) {
506 char buf
[FORMAT_TIMESPAN_MAX
];
507 log_debug("Exiting worker, ran for %s, that's enough.",
508 format_timespan(buf
, sizeof(buf
), usec_sub_unsigned(n
, start_time
), 0));
512 if (last_busy_usec
== USEC_INFINITY
)
514 else if (listen_idle_usec
!= USEC_INFINITY
&& n
>= usec_add(last_busy_usec
, listen_idle_usec
)) {
515 char buf
[FORMAT_TIMESPAN_MAX
];
516 log_debug("Exiting worker, been idle for %s.",
517 format_timespan(buf
, sizeof(buf
), usec_sub_unsigned(n
, last_busy_usec
), 0));
521 (void) rename_process("systemd-userwork: waiting...");
523 fd
= accept4(listen_fd
, NULL
, NULL
, SOCK_NONBLOCK
|SOCK_CLOEXEC
);
527 (void) rename_process("systemd-userwork: processing...");
530 continue; /* The listening socket has SO_RECVTIMEO set, hence a timeout is expected
531 * after a while, let's check if it's time to exit though. */
533 continue; /* Might be that somebody attached via strace, let's just continue in that
536 return log_error_errno(fd
, "Failed to accept() from listening socket: %m");
538 if (now(CLOCK_MONOTONIC
) <= usec_add(n
, PRESSURE_SLEEP_TIME_USEC
)) {
539 /* We only slept a very short time? If so, let's see if there are more sockets
540 * pending, and if so, let's ask our parent for more workers */
542 r
= fd_wait_for_event(listen_fd
, POLLIN
, 0);
544 return log_error_errno(r
, "Failed to test for POLLIN on listening socket: %m");
546 if (FLAGS_SET(r
, POLLIN
)) {
551 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Parent already died?");
553 if (kill(parent
, SIGUSR2
) < 0)
554 return log_error_errno(errno
, "Failed to kill our own parent.");
558 (void) process_connection(server
, TAKE_FD(fd
));
559 last_busy_usec
= USEC_INFINITY
;
565 DEFINE_MAIN_FUNCTION(run
);