1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "conf-files.h"
6 #include "dirent-util.h"
7 #include "dlfcn-util.h"
8 #include "errno-util.h"
10 #include "format-util.h"
11 #include "missing_syscall.h"
12 #include "parse-util.h"
14 #include "socket-util.h"
16 #include "user-record-nss.h"
17 #include "user-util.h"
18 #include "userdb-dropin.h"
22 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(link_hash_ops
, void, trivial_hash_func
, trivial_compare_func
, Varlink
, varlink_unref
);
24 typedef enum LookupWhat
{
31 struct UserDBIterator
{
37 bool dropin_covered
:1;
38 bool synthesize_root
:1;
39 bool synthesize_nobody
:1;
40 bool nss_systemd_blocked
:1;
42 size_t current_dropin
;
46 UserRecord
*found_user
; /* when .what == LOOKUP_USER */
47 GroupRecord
*found_group
; /* when .what == LOOKUP_GROUP */
49 char *found_user_name
, *found_group_name
; /* when .what == LOOKUP_MEMBERSHIP */
50 char **members_of_group
;
51 size_t index_members_of_group
;
52 char *filter_user_name
, *filter_group_name
;
55 UserDBIterator
* userdb_iterator_free(UserDBIterator
*iterator
) {
59 set_free(iterator
->links
);
60 strv_free(iterator
->dropins
);
62 switch (iterator
->what
) {
65 user_record_unref(iterator
->found_user
);
67 if (iterator
->nss_iterating
)
73 group_record_unref(iterator
->found_group
);
75 if (iterator
->nss_iterating
)
80 case LOOKUP_MEMBERSHIP
:
81 free(iterator
->found_user_name
);
82 free(iterator
->found_group_name
);
83 strv_free(iterator
->members_of_group
);
84 free(iterator
->filter_user_name
);
85 free(iterator
->filter_group_name
);
87 if (iterator
->nss_iterating
)
96 sd_event_unref(iterator
->event
);
98 if (iterator
->nss_systemd_blocked
)
99 assert_se(userdb_block_nss_systemd(false) >= 0);
101 return mfree(iterator
);
104 static UserDBIterator
* userdb_iterator_new(LookupWhat what
, UserDBFlags flags
) {
108 assert(what
< _LOOKUP_WHAT_MAX
);
110 i
= new(UserDBIterator
, 1);
114 *i
= (UserDBIterator
) {
117 .synthesize_root
= !FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
),
118 .synthesize_nobody
= !FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
),
124 static int userdb_iterator_block_nss_systemd(UserDBIterator
*iterator
) {
129 if (iterator
->nss_systemd_blocked
)
132 r
= userdb_block_nss_systemd(true);
136 iterator
->nss_systemd_blocked
= true;
140 struct user_group_data
{
145 static void user_group_data_done(struct user_group_data
*d
) {
146 json_variant_unref(d
->record
);
149 struct membership_data
{
154 static void membership_data_done(struct membership_data
*d
) {
159 static int userdb_on_query_reply(
161 JsonVariant
*parameters
,
162 const char *error_id
,
163 VarlinkReplyFlags flags
,
166 UserDBIterator
*iterator
= ASSERT_PTR(userdata
);
170 log_debug("Got lookup error: %s", error_id
);
172 if (STR_IN_SET(error_id
,
173 "io.systemd.UserDatabase.NoRecordFound",
174 "io.systemd.UserDatabase.ConflictingRecordFound"))
176 else if (streq(error_id
, "io.systemd.UserDatabase.ServiceNotAvailable"))
178 else if (streq(error_id
, "io.systemd.UserDatabase.EnumerationNotSupported"))
180 else if (streq(error_id
, VARLINK_ERROR_TIMEOUT
))
188 switch (iterator
->what
) {
191 _cleanup_(user_group_data_done
) struct user_group_data user_data
= {};
193 static const JsonDispatch dispatch_table
[] = {
194 { "record", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_variant
, offsetof(struct user_group_data
, record
), 0 },
195 { "incomplete", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(struct user_group_data
, incomplete
), 0 },
198 _cleanup_(user_record_unrefp
) UserRecord
*hr
= NULL
;
200 assert_se(!iterator
->found_user
);
202 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &user_data
);
206 if (!user_data
.record
) {
207 r
= log_debug_errno(SYNTHETIC_ERRNO(EIO
), "Reply is missing record key");
211 hr
= user_record_new();
217 r
= user_record_load(hr
, user_data
.record
, USER_RECORD_LOAD_REFUSE_SECRET
|USER_RECORD_PERMISSIVE
);
222 r
= log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "User record does not carry service information, refusing.");
226 hr
->incomplete
= user_data
.incomplete
;
228 /* We match the root user by the name since the name is our primary key. We match the nobody
229 * use by UID though, since the name might differ on OSes */
230 if (streq_ptr(hr
->user_name
, "root"))
231 iterator
->synthesize_root
= false;
232 if (hr
->uid
== UID_NOBODY
)
233 iterator
->synthesize_nobody
= false;
235 iterator
->found_user
= TAKE_PTR(hr
);
238 /* More stuff coming? then let's just exit cleanly here */
239 if (FLAGS_SET(flags
, VARLINK_REPLY_CONTINUES
))
242 /* Otherwise, let's remove this link and exit cleanly then */
248 _cleanup_(user_group_data_done
) struct user_group_data group_data
= {};
250 static const JsonDispatch dispatch_table
[] = {
251 { "record", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_variant
, offsetof(struct user_group_data
, record
), 0 },
252 { "incomplete", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(struct user_group_data
, incomplete
), 0 },
255 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
257 assert_se(!iterator
->found_group
);
259 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &group_data
);
263 if (!group_data
.record
) {
264 r
= log_debug_errno(SYNTHETIC_ERRNO(EIO
), "Reply is missing record key");
268 g
= group_record_new();
274 r
= group_record_load(g
, group_data
.record
, USER_RECORD_LOAD_REFUSE_SECRET
|USER_RECORD_PERMISSIVE
);
279 r
= log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Group record does not carry service information, refusing.");
283 g
->incomplete
= group_data
.incomplete
;
285 if (streq_ptr(g
->group_name
, "root"))
286 iterator
->synthesize_root
= false;
287 if (g
->gid
== GID_NOBODY
)
288 iterator
->synthesize_nobody
= false;
290 iterator
->found_group
= TAKE_PTR(g
);
293 if (FLAGS_SET(flags
, VARLINK_REPLY_CONTINUES
))
300 case LOOKUP_MEMBERSHIP
: {
301 _cleanup_(membership_data_done
) struct membership_data membership_data
= {};
303 static const JsonDispatch dispatch_table
[] = {
304 { "userName", JSON_VARIANT_STRING
, json_dispatch_user_group_name
, offsetof(struct membership_data
, user_name
), JSON_RELAX
},
305 { "groupName", JSON_VARIANT_STRING
, json_dispatch_user_group_name
, offsetof(struct membership_data
, group_name
), JSON_RELAX
},
309 assert(!iterator
->found_user_name
);
310 assert(!iterator
->found_group_name
);
312 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &membership_data
);
316 iterator
->found_user_name
= TAKE_PTR(membership_data
.user_name
);
317 iterator
->found_group_name
= TAKE_PTR(membership_data
.group_name
);
320 if (FLAGS_SET(flags
, VARLINK_REPLY_CONTINUES
))
328 assert_not_reached();
332 /* If we got one ESRCH, let that win. This way when we do a wild dump we won't be tripped up by bad
333 * errors if at least one connection ended cleanly */
334 if (r
== -ESRCH
|| iterator
->error
== 0)
335 iterator
->error
= -r
;
337 assert_se(set_remove(iterator
->links
, link
) == link
);
338 link
= varlink_unref(link
);
342 static int userdb_connect(
343 UserDBIterator
*iterator
,
347 JsonVariant
*query
) {
349 _cleanup_(varlink_unrefp
) Varlink
*vl
= NULL
;
356 r
= varlink_connect_address(&vl
, path
);
358 return log_debug_errno(r
, "Unable to connect to %s: %m", path
);
360 varlink_set_userdata(vl
, iterator
);
362 if (!iterator
->event
) {
363 r
= sd_event_new(&iterator
->event
);
365 return log_debug_errno(r
, "Unable to allocate event loop: %m");
368 r
= varlink_attach_event(vl
, iterator
->event
, SD_EVENT_PRIORITY_NORMAL
);
370 return log_debug_errno(r
, "Failed to attach varlink connection to event loop: %m");
372 (void) varlink_set_description(vl
, path
);
374 r
= varlink_bind_reply(vl
, userdb_on_query_reply
);
376 return log_debug_errno(r
, "Failed to bind reply callback: %m");
379 r
= varlink_observe(vl
, method
, query
);
381 r
= varlink_invoke(vl
, method
, query
);
383 return log_debug_errno(r
, "Failed to invoke varlink method: %m");
385 r
= set_ensure_consume(&iterator
->links
, &link_hash_ops
, TAKE_PTR(vl
));
387 return log_debug_errno(r
, "Failed to add varlink connection to set: %m");
391 static int userdb_start_query(
392 UserDBIterator
*iterator
,
398 _cleanup_strv_free_
char **except
= NULL
, **only
= NULL
;
399 _cleanup_closedir_
DIR *d
= NULL
;
406 if (FLAGS_SET(flags
, USERDB_EXCLUDE_VARLINK
))
409 e
= getenv("SYSTEMD_BYPASS_USERDB");
411 r
= parse_boolean(e
);
415 except
= strv_split(e
, ":");
421 e
= getenv("SYSTEMD_ONLY_USERDB");
423 only
= strv_split(e
, ":");
428 /* First, let's talk to the multiplexer, if we can */
429 if ((flags
& (USERDB_AVOID_MULTIPLEXER
|USERDB_EXCLUDE_DYNAMIC_USER
|USERDB_EXCLUDE_NSS
|USERDB_EXCLUDE_DROPIN
|USERDB_DONT_SYNTHESIZE
)) == 0 &&
430 !strv_contains(except
, "io.systemd.Multiplexer") &&
431 (!only
|| strv_contains(only
, "io.systemd.Multiplexer"))) {
432 _cleanup_(json_variant_unrefp
) JsonVariant
*patched_query
= json_variant_ref(query
);
434 r
= json_variant_set_field_string(&patched_query
, "service", "io.systemd.Multiplexer");
436 return log_debug_errno(r
, "Unable to set service JSON field: %m");
438 r
= userdb_connect(iterator
, "/run/systemd/userdb/io.systemd.Multiplexer", method
, more
, patched_query
);
440 iterator
->nss_covered
= true; /* The multiplexer does NSS */
441 iterator
->dropin_covered
= true; /* It also handles drop-in stuff */
446 d
= opendir("/run/systemd/userdb/");
454 FOREACH_DIRENT(de
, d
, return -errno
) {
455 _cleanup_(json_variant_unrefp
) JsonVariant
*patched_query
= NULL
;
456 _cleanup_free_
char *p
= NULL
;
457 bool is_nss
, is_dropin
;
459 if (streq(de
->d_name
, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
462 if (FLAGS_SET(flags
, USERDB_EXCLUDE_DYNAMIC_USER
) &&
463 streq(de
->d_name
, "io.systemd.DynamicUser"))
466 /* Avoid NSS if this is requested. Note that we also skip NSS when we were asked to skip the
467 * multiplexer, since in that case it's safer to do NSS in the client side emulation below
468 * (and when we run as part of systemd-userdbd.service we don't want to talk to ourselves
470 is_nss
= streq(de
->d_name
, "io.systemd.NameServiceSwitch");
471 if ((flags
& (USERDB_EXCLUDE_NSS
|USERDB_AVOID_MULTIPLEXER
)) && is_nss
)
474 /* Similar for the drop-in service */
475 is_dropin
= streq(de
->d_name
, "io.systemd.DropIn");
476 if ((flags
& (USERDB_EXCLUDE_DROPIN
|USERDB_AVOID_MULTIPLEXER
)) && is_dropin
)
479 if (strv_contains(except
, de
->d_name
))
482 if (only
&& !strv_contains(only
, de
->d_name
))
485 p
= path_join("/run/systemd/userdb/", de
->d_name
);
489 patched_query
= json_variant_ref(query
);
490 r
= json_variant_set_field_string(&patched_query
, "service", de
->d_name
);
492 return log_debug_errno(r
, "Unable to set service JSON field: %m");
494 r
= userdb_connect(iterator
, p
, method
, more
, patched_query
);
495 if (is_nss
&& r
>= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
496 * and could connect to it */
497 iterator
->nss_covered
= true;
498 if (is_dropin
&& r
>= 0)
499 iterator
->dropin_covered
= true;
501 if (ret
== 0 && r
< 0)
505 if (set_isempty(iterator
->links
))
506 return ret
< 0 ? ret
: -ESRCH
; /* propagate last error we saw if we couldn't connect to anything. */
508 /* We connected to some services, in this case, ignore the ones we failed on */
512 static int userdb_process(
513 UserDBIterator
*iterator
,
514 UserRecord
**ret_user_record
,
515 GroupRecord
**ret_group_record
,
516 char **ret_user_name
,
517 char **ret_group_name
) {
524 if (iterator
->what
== LOOKUP_USER
&& iterator
->found_user
) {
526 *ret_user_record
= TAKE_PTR(iterator
->found_user
);
528 iterator
->found_user
= user_record_unref(iterator
->found_user
);
530 if (ret_group_record
)
531 *ret_group_record
= NULL
;
533 *ret_user_name
= NULL
;
535 *ret_group_name
= NULL
;
540 if (iterator
->what
== LOOKUP_GROUP
&& iterator
->found_group
) {
541 if (ret_group_record
)
542 *ret_group_record
= TAKE_PTR(iterator
->found_group
);
544 iterator
->found_group
= group_record_unref(iterator
->found_group
);
547 *ret_user_record
= NULL
;
549 *ret_user_name
= NULL
;
551 *ret_group_name
= NULL
;
556 if (iterator
->what
== LOOKUP_MEMBERSHIP
&& iterator
->found_user_name
&& iterator
->found_group_name
) {
558 *ret_user_name
= TAKE_PTR(iterator
->found_user_name
);
560 iterator
->found_user_name
= mfree(iterator
->found_user_name
);
563 *ret_group_name
= TAKE_PTR(iterator
->found_group_name
);
565 iterator
->found_group_name
= mfree(iterator
->found_group_name
);
568 *ret_user_record
= NULL
;
569 if (ret_group_record
)
570 *ret_group_record
= NULL
;
575 if (set_isempty(iterator
->links
)) {
576 if (iterator
->error
== 0)
579 return -abs(iterator
->error
);
582 if (!iterator
->event
)
585 r
= sd_event_run(iterator
->event
, UINT64_MAX
);
591 static int synthetic_root_user_build(UserRecord
**ret
) {
592 return user_record_build(
594 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING("root")),
595 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(0)),
596 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
597 JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/root")),
598 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
601 static int synthetic_nobody_user_build(UserRecord
**ret
) {
602 return user_record_build(
604 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING(NOBODY_USER_NAME
)),
605 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(UID_NOBODY
)),
606 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY
)),
607 JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN
)),
608 JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
609 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
612 int userdb_by_name(const char *name
, UserDBFlags flags
, UserRecord
**ret
) {
613 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
614 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
617 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
620 r
= json_build(&query
, JSON_BUILD_OBJECT(
621 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name
))));
625 iterator
= userdb_iterator_new(LOOKUP_USER
, flags
);
629 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetUserRecord", false, query
, flags
);
631 r
= userdb_process(iterator
, ret
, NULL
, NULL
, NULL
);
636 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !iterator
->dropin_covered
) {
637 r
= dropin_user_record_by_name(name
, NULL
, flags
, ret
);
642 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !iterator
->nss_covered
) {
643 /* Make sure the NSS lookup doesn't recurse back to us. */
645 r
= userdb_iterator_block_nss_systemd(iterator
);
647 /* Client-side NSS fallback */
648 r
= nss_user_record_by_name(name
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
654 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
655 if (streq(name
, "root"))
656 return synthetic_root_user_build(ret
);
658 if (streq(name
, NOBODY_USER_NAME
) && synthesize_nobody())
659 return synthetic_nobody_user_build(ret
);
665 int userdb_by_uid(uid_t uid
, UserDBFlags flags
, UserRecord
**ret
) {
666 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
667 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
670 if (!uid_is_valid(uid
))
673 r
= json_build(&query
, JSON_BUILD_OBJECT(
674 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid
))));
678 iterator
= userdb_iterator_new(LOOKUP_USER
, flags
);
682 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetUserRecord", false, query
, flags
);
684 r
= userdb_process(iterator
, ret
, NULL
, NULL
, NULL
);
689 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !iterator
->dropin_covered
) {
690 r
= dropin_user_record_by_uid(uid
, NULL
, flags
, ret
);
695 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !iterator
->nss_covered
) {
696 r
= userdb_iterator_block_nss_systemd(iterator
);
698 /* Client-side NSS fallback */
699 r
= nss_user_record_by_uid(uid
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
705 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
707 return synthetic_root_user_build(ret
);
709 if (uid
== UID_NOBODY
&& synthesize_nobody())
710 return synthetic_nobody_user_build(ret
);
716 int userdb_all(UserDBFlags flags
, UserDBIterator
**ret
) {
717 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
722 iterator
= userdb_iterator_new(LOOKUP_USER
, flags
);
726 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetUserRecord", true, NULL
, flags
);
728 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
729 r
= userdb_iterator_block_nss_systemd(iterator
);
734 iterator
->nss_iterating
= true;
737 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
)) {
738 r
= conf_files_list_nulstr(
742 CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
,
743 USERDB_DROPIN_DIR_NULSTR("userdb"));
745 log_debug_errno(r
, "Failed to find user drop-ins, ignoring: %m");
748 /* propagate IPC error, but only if there are no drop-ins */
750 !iterator
->nss_iterating
&&
751 strv_isempty(iterator
->dropins
))
754 *ret
= TAKE_PTR(iterator
);
758 int userdb_iterator_get(UserDBIterator
*iterator
, UserRecord
**ret
) {
762 assert(iterator
->what
== LOOKUP_USER
);
764 if (iterator
->nss_iterating
) {
767 /* If NSS isn't covered elsewhere, let's iterate through it first, since it probably contains
768 * the more traditional sources, which are probably good to show first. */
772 _cleanup_free_
char *buffer
= NULL
;
773 bool incomplete
= false;
776 if (streq_ptr(pw
->pw_name
, "root"))
777 iterator
->synthesize_root
= false;
778 if (pw
->pw_uid
== UID_NOBODY
)
779 iterator
->synthesize_nobody
= false;
781 if (!FLAGS_SET(iterator
->flags
, USERDB_SUPPRESS_SHADOW
)) {
782 r
= nss_spwd_for_passwd(pw
, &spwd
, &buffer
);
784 log_debug_errno(r
, "Failed to acquire shadow entry for user %s, ignoring: %m", pw
->pw_name
);
785 incomplete
= ERRNO_IS_PRIVILEGE(r
);
792 r
= nss_passwd_to_user_record(pw
, r
>= 0 ? &spwd
: NULL
, ret
);
797 (*ret
)->incomplete
= incomplete
;
804 log_debug_errno(errno
, "Failure to iterate NSS user database, ignoring: %m");
806 iterator
->nss_iterating
= false;
810 for (; iterator
->dropins
&& iterator
->dropins
[iterator
->current_dropin
]; iterator
->current_dropin
++) {
811 const char *i
= iterator
->dropins
[iterator
->current_dropin
];
812 _cleanup_free_
char *fn
= NULL
;
816 /* Next, let's add in the static drop-ins, which are quick to retrieve */
818 r
= path_extract_filename(i
, &fn
);
822 e
= endswith(fn
, ".user"); /* not actually a .user file? Then skip to next */
826 *e
= 0; /* Chop off suffix */
828 if (parse_uid(fn
, &uid
) < 0) /* not a UID .user file? Then skip to next */
831 r
= dropin_user_record_by_uid(uid
, i
, iterator
->flags
, ret
);
833 log_debug_errno(r
, "Failed to parse user record for UID " UID_FMT
", ignoring: %m", uid
);
834 continue; /* If we failed to parse this record, let's suppress it from enumeration,
835 * and continue with the next record. Maybe someone is dropping it files
836 * and only partially wrote this one. */
839 iterator
->current_dropin
++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
844 /* Then, let's return the users provided by varlink IPC */
845 r
= userdb_process(iterator
, ret
, NULL
, NULL
, NULL
);
848 /* Finally, synthesize root + nobody if not done yet */
849 if (iterator
->synthesize_root
) {
850 iterator
->synthesize_root
= false;
852 return synthetic_root_user_build(ret
);
855 if (iterator
->synthesize_nobody
) {
856 iterator
->synthesize_nobody
= false;
858 return synthetic_nobody_user_build(ret
);
861 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
862 if (iterator
->n_found
> 0)
869 static int synthetic_root_group_build(GroupRecord
**ret
) {
870 return group_record_build(
872 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING("root")),
873 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
874 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
877 static int synthetic_nobody_group_build(GroupRecord
**ret
) {
878 return group_record_build(
880 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING(NOBODY_GROUP_NAME
)),
881 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY
)),
882 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
885 int groupdb_by_name(const char *name
, UserDBFlags flags
, GroupRecord
**ret
) {
886 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
887 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
890 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
893 r
= json_build(&query
, JSON_BUILD_OBJECT(
894 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name
))));
898 iterator
= userdb_iterator_new(LOOKUP_GROUP
, flags
);
902 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetGroupRecord", false, query
, flags
);
904 r
= userdb_process(iterator
, NULL
, ret
, NULL
, NULL
);
909 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !(iterator
&& iterator
->dropin_covered
)) {
910 r
= dropin_group_record_by_name(name
, NULL
, flags
, ret
);
916 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !(iterator
&& iterator
->nss_covered
)) {
917 r
= userdb_iterator_block_nss_systemd(iterator
);
919 r
= nss_group_record_by_name(name
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
925 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
926 if (streq(name
, "root"))
927 return synthetic_root_group_build(ret
);
929 if (streq(name
, NOBODY_GROUP_NAME
) && synthesize_nobody())
930 return synthetic_nobody_group_build(ret
);
936 int groupdb_by_gid(gid_t gid
, UserDBFlags flags
, GroupRecord
**ret
) {
937 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
938 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
941 if (!gid_is_valid(gid
))
944 r
= json_build(&query
, JSON_BUILD_OBJECT(
945 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid
))));
949 iterator
= userdb_iterator_new(LOOKUP_GROUP
, flags
);
953 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetGroupRecord", false, query
, flags
);
955 r
= userdb_process(iterator
, NULL
, ret
, NULL
, NULL
);
960 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !(iterator
&& iterator
->dropin_covered
)) {
961 r
= dropin_group_record_by_gid(gid
, NULL
, flags
, ret
);
966 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !(iterator
&& iterator
->nss_covered
)) {
967 r
= userdb_iterator_block_nss_systemd(iterator
);
969 r
= nss_group_record_by_gid(gid
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
975 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
977 return synthetic_root_group_build(ret
);
979 if (gid
== GID_NOBODY
&& synthesize_nobody())
980 return synthetic_nobody_group_build(ret
);
986 int groupdb_all(UserDBFlags flags
, UserDBIterator
**ret
) {
987 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
992 iterator
= userdb_iterator_new(LOOKUP_GROUP
, flags
);
996 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetGroupRecord", true, NULL
, flags
);
998 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
999 r
= userdb_iterator_block_nss_systemd(iterator
);
1004 iterator
->nss_iterating
= true;
1007 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
)) {
1008 r
= conf_files_list_nulstr(
1012 CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
,
1013 USERDB_DROPIN_DIR_NULSTR("userdb"));
1015 log_debug_errno(r
, "Failed to find group drop-ins, ignoring: %m");
1019 !iterator
->nss_iterating
&&
1020 strv_isempty(iterator
->dropins
))
1023 *ret
= TAKE_PTR(iterator
);
1027 int groupdb_iterator_get(UserDBIterator
*iterator
, GroupRecord
**ret
) {
1031 assert(iterator
->what
== LOOKUP_GROUP
);
1033 if (iterator
->nss_iterating
) {
1039 _cleanup_free_
char *buffer
= NULL
;
1040 bool incomplete
= false;
1043 if (streq_ptr(gr
->gr_name
, "root"))
1044 iterator
->synthesize_root
= false;
1045 if (gr
->gr_gid
== GID_NOBODY
)
1046 iterator
->synthesize_nobody
= false;
1048 if (!FLAGS_SET(iterator
->flags
, USERDB_SUPPRESS_SHADOW
)) {
1049 r
= nss_sgrp_for_group(gr
, &sgrp
, &buffer
);
1051 log_debug_errno(r
, "Failed to acquire shadow entry for group %s, ignoring: %m", gr
->gr_name
);
1052 incomplete
= ERRNO_IS_PRIVILEGE(r
);
1059 r
= nss_group_to_group_record(gr
, r
>= 0 ? &sgrp
: NULL
, ret
);
1064 (*ret
)->incomplete
= incomplete
;
1066 iterator
->n_found
++;
1071 log_debug_errno(errno
, "Failure to iterate NSS group database, ignoring: %m");
1073 iterator
->nss_iterating
= false;
1077 for (; iterator
->dropins
&& iterator
->dropins
[iterator
->current_dropin
]; iterator
->current_dropin
++) {
1078 const char *i
= iterator
->dropins
[iterator
->current_dropin
];
1079 _cleanup_free_
char *fn
= NULL
;
1083 r
= path_extract_filename(i
, &fn
);
1087 e
= endswith(fn
, ".group");
1091 *e
= 0; /* Chop off suffix */
1093 if (parse_gid(fn
, &gid
) < 0)
1096 r
= dropin_group_record_by_gid(gid
, i
, iterator
->flags
, ret
);
1098 log_debug_errno(r
, "Failed to parse group record for GID " GID_FMT
", ignoring: %m", gid
);
1102 iterator
->current_dropin
++;
1103 iterator
->n_found
++;
1107 r
= userdb_process(iterator
, NULL
, ret
, NULL
, NULL
);
1109 if (iterator
->synthesize_root
) {
1110 iterator
->synthesize_root
= false;
1111 iterator
->n_found
++;
1112 return synthetic_root_group_build(ret
);
1115 if (iterator
->synthesize_nobody
) {
1116 iterator
->synthesize_nobody
= false;
1117 iterator
->n_found
++;
1118 return synthetic_nobody_group_build(ret
);
1121 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1122 if (iterator
->n_found
> 0)
1129 static void discover_membership_dropins(UserDBIterator
*i
, UserDBFlags flags
) {
1132 r
= conf_files_list_nulstr(
1136 CONF_FILES_REGULAR
|CONF_FILES_BASENAME
|CONF_FILES_FILTER_MASKED
,
1137 USERDB_DROPIN_DIR_NULSTR("userdb"));
1139 log_debug_errno(r
, "Failed to find membership drop-ins, ignoring: %m");
1142 int membershipdb_by_user(const char *name
, UserDBFlags flags
, UserDBIterator
**ret
) {
1143 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1144 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
1149 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
1152 r
= json_build(&query
, JSON_BUILD_OBJECT(
1153 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name
))));
1157 iterator
= userdb_iterator_new(LOOKUP_MEMBERSHIP
, flags
);
1161 iterator
->filter_user_name
= strdup(name
);
1162 if (!iterator
->filter_user_name
)
1165 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetMemberships", true, query
, flags
);
1167 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
1168 r
= userdb_iterator_block_nss_systemd(iterator
);
1173 iterator
->nss_iterating
= true;
1176 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
))
1177 discover_membership_dropins(iterator
, flags
);
1180 !iterator
->nss_iterating
&&
1181 strv_isempty(iterator
->dropins
))
1184 *ret
= TAKE_PTR(iterator
);
1188 int membershipdb_by_group(const char *name
, UserDBFlags flags
, UserDBIterator
**ret
) {
1189 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1190 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
1195 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
1198 r
= json_build(&query
, JSON_BUILD_OBJECT(
1199 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name
))));
1203 iterator
= userdb_iterator_new(LOOKUP_MEMBERSHIP
, flags
);
1207 iterator
->filter_group_name
= strdup(name
);
1208 if (!iterator
->filter_group_name
)
1211 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetMemberships", true, query
, flags
);
1213 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
1214 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
1216 r
= userdb_iterator_block_nss_systemd(iterator
);
1220 /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
1221 (void) nss_group_record_by_name(name
, false, &gr
);
1223 iterator
->members_of_group
= strv_copy(gr
->members
);
1224 if (!iterator
->members_of_group
)
1227 iterator
->index_members_of_group
= 0;
1229 iterator
->found_group_name
= strdup(name
);
1230 if (!iterator
->found_group_name
)
1235 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
))
1236 discover_membership_dropins(iterator
, flags
);
1239 strv_isempty(iterator
->members_of_group
) &&
1240 strv_isempty(iterator
->dropins
))
1243 *ret
= TAKE_PTR(iterator
);
1247 int membershipdb_all(UserDBFlags flags
, UserDBIterator
**ret
) {
1248 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1253 iterator
= userdb_iterator_new(LOOKUP_MEMBERSHIP
, flags
);
1257 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetMemberships", true, NULL
, flags
);
1259 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
1260 r
= userdb_iterator_block_nss_systemd(iterator
);
1265 iterator
->nss_iterating
= true;
1268 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
))
1269 discover_membership_dropins(iterator
, flags
);
1272 !iterator
->nss_iterating
&&
1273 strv_isempty(iterator
->dropins
))
1276 *ret
= TAKE_PTR(iterator
);
1280 int membershipdb_iterator_get(
1281 UserDBIterator
*iterator
,
1290 /* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
1291 if (!iterator
->members_of_group
) {
1294 if (!iterator
->nss_iterating
)
1297 assert(!iterator
->found_user_name
);
1303 log_debug_errno(errno
, "Failure during NSS group iteration, ignoring: %m");
1307 } while (iterator
->filter_user_name
? !strv_contains(g
->gr_mem
, iterator
->filter_user_name
) :
1308 strv_isempty(g
->gr_mem
));
1311 r
= free_and_strdup(&iterator
->found_group_name
, g
->gr_name
);
1315 if (iterator
->filter_user_name
)
1316 iterator
->members_of_group
= strv_new(iterator
->filter_user_name
);
1318 iterator
->members_of_group
= strv_copy(g
->gr_mem
);
1319 if (!iterator
->members_of_group
)
1322 iterator
->index_members_of_group
= 0;
1324 iterator
->nss_iterating
= false;
1330 assert(iterator
->found_group_name
);
1331 assert(iterator
->members_of_group
);
1332 assert(!iterator
->found_user_name
);
1334 if (iterator
->members_of_group
[iterator
->index_members_of_group
]) {
1335 _cleanup_free_
char *cu
= NULL
, *cg
= NULL
;
1338 cu
= strdup(iterator
->members_of_group
[iterator
->index_members_of_group
]);
1344 cg
= strdup(iterator
->found_group_name
);
1350 *ret_user
= TAKE_PTR(cu
);
1353 *ret_group
= TAKE_PTR(cg
);
1355 iterator
->index_members_of_group
++;
1359 iterator
->members_of_group
= strv_free(iterator
->members_of_group
);
1360 iterator
->found_group_name
= mfree(iterator
->found_group_name
);
1363 for (; iterator
->dropins
&& iterator
->dropins
[iterator
->current_dropin
]; iterator
->current_dropin
++) {
1364 const char *i
= iterator
->dropins
[iterator
->current_dropin
], *e
, *c
;
1365 _cleanup_free_
char *un
= NULL
, *gn
= NULL
;
1367 e
= endswith(i
, ".membership");
1371 c
= memchr(i
, ':', e
- i
);
1375 un
= strndup(i
, c
- i
);
1378 if (iterator
->filter_user_name
) {
1379 if (!streq(un
, iterator
->filter_user_name
))
1381 } else if (!valid_user_group_name(un
, VALID_USER_RELAX
))
1384 c
++; /* skip over ':' */
1385 gn
= strndup(c
, e
- c
);
1388 if (iterator
->filter_group_name
) {
1389 if (!streq(gn
, iterator
->filter_group_name
))
1391 } else if (!valid_user_group_name(gn
, VALID_USER_RELAX
))
1394 iterator
->current_dropin
++;
1395 iterator
->n_found
++;
1398 *ret_user
= TAKE_PTR(un
);
1400 *ret_group
= TAKE_PTR(gn
);
1405 r
= userdb_process(iterator
, NULL
, NULL
, ret_user
, ret_group
);
1406 if (r
< 0 && iterator
->n_found
> 0)
1412 int membershipdb_by_group_strv(const char *name
, UserDBFlags flags
, char ***ret
) {
1413 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1414 _cleanup_strv_free_
char **members
= NULL
;
1420 r
= membershipdb_by_group(name
, flags
, &iterator
);
1425 _cleanup_free_
char *user_name
= NULL
;
1427 r
= membershipdb_iterator_get(iterator
, &user_name
, NULL
);
1433 r
= strv_consume(&members
, TAKE_PTR(user_name
));
1441 *ret
= TAKE_PTR(members
);
1445 int userdb_block_nss_systemd(int b
) {
1446 _cleanup_(dlclosep
) void *dl
= NULL
;
1447 int (*call
)(bool b
);
1449 /* Note that we might be called from libnss_systemd.so.2 itself, but that should be fine, really. */
1451 dl
= dlopen(ROOTLIBDIR
"/libnss_systemd.so.2", RTLD_LAZY
|RTLD_NODELETE
);
1453 /* If the file isn't installed, don't complain loudly */
1454 log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
1458 call
= (int (*)(bool b
)) dlsym(dl
, "_nss_systemd_block");
1460 /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
1461 return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD
),
1462 "Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());