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_release(struct user_group_data
*d
) {
146 json_variant_unref(d
->record
);
149 static int userdb_on_query_reply(
151 JsonVariant
*parameters
,
152 const char *error_id
,
153 VarlinkReplyFlags flags
,
156 UserDBIterator
*iterator
= userdata
;
162 log_debug("Got lookup error: %s", error_id
);
164 if (STR_IN_SET(error_id
,
165 "io.systemd.UserDatabase.NoRecordFound",
166 "io.systemd.UserDatabase.ConflictingRecordFound"))
168 else if (streq(error_id
, "io.systemd.UserDatabase.ServiceNotAvailable"))
170 else if (streq(error_id
, "io.systemd.UserDatabase.EnumerationNotSupported"))
172 else if (streq(error_id
, VARLINK_ERROR_TIMEOUT
))
180 switch (iterator
->what
) {
183 _cleanup_(user_group_data_release
) struct user_group_data user_data
= {};
185 static const JsonDispatch dispatch_table
[] = {
186 { "record", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_variant
, offsetof(struct user_group_data
, record
), 0 },
187 { "incomplete", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(struct user_group_data
, incomplete
), 0 },
190 _cleanup_(user_record_unrefp
) UserRecord
*hr
= NULL
;
192 assert_se(!iterator
->found_user
);
194 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &user_data
);
198 if (!user_data
.record
) {
199 r
= log_debug_errno(SYNTHETIC_ERRNO(EIO
), "Reply is missing record key");
203 hr
= user_record_new();
209 r
= user_record_load(hr
, user_data
.record
, USER_RECORD_LOAD_REFUSE_SECRET
|USER_RECORD_PERMISSIVE
);
214 r
= log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "User record does not carry service information, refusing.");
218 hr
->incomplete
= user_data
.incomplete
;
220 /* We match the root user by the name since the name is our primary key. We match the nobody
221 * use by UID though, since the name might differ on OSes */
222 if (streq_ptr(hr
->user_name
, "root"))
223 iterator
->synthesize_root
= false;
224 if (hr
->uid
== UID_NOBODY
)
225 iterator
->synthesize_nobody
= false;
227 iterator
->found_user
= TAKE_PTR(hr
);
230 /* More stuff coming? then let's just exit cleanly here */
231 if (FLAGS_SET(flags
, VARLINK_REPLY_CONTINUES
))
234 /* Otherwise, let's remove this link and exit cleanly then */
240 _cleanup_(user_group_data_release
) struct user_group_data group_data
= {};
242 static const JsonDispatch dispatch_table
[] = {
243 { "record", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_variant
, offsetof(struct user_group_data
, record
), 0 },
244 { "incomplete", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(struct user_group_data
, incomplete
), 0 },
247 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
249 assert_se(!iterator
->found_group
);
251 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &group_data
);
255 if (!group_data
.record
) {
256 r
= log_debug_errno(SYNTHETIC_ERRNO(EIO
), "Reply is missing record key");
260 g
= group_record_new();
266 r
= group_record_load(g
, group_data
.record
, USER_RECORD_LOAD_REFUSE_SECRET
|USER_RECORD_PERMISSIVE
);
271 r
= log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Group record does not carry service information, refusing.");
275 g
->incomplete
= group_data
.incomplete
;
277 if (streq_ptr(g
->group_name
, "root"))
278 iterator
->synthesize_root
= false;
279 if (g
->gid
== GID_NOBODY
)
280 iterator
->synthesize_nobody
= false;
282 iterator
->found_group
= TAKE_PTR(g
);
285 if (FLAGS_SET(flags
, VARLINK_REPLY_CONTINUES
))
292 case LOOKUP_MEMBERSHIP
: {
293 struct membership_data
{
294 const char *user_name
;
295 const char *group_name
;
296 } membership_data
= {};
298 static const JsonDispatch dispatch_table
[] = {
299 { "userName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(struct membership_data
, user_name
), JSON_SAFE
},
300 { "groupName", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(struct membership_data
, group_name
), JSON_SAFE
},
304 assert(!iterator
->found_user_name
);
305 assert(!iterator
->found_group_name
);
307 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &membership_data
);
311 iterator
->found_user_name
= mfree(iterator
->found_user_name
);
312 iterator
->found_group_name
= mfree(iterator
->found_group_name
);
314 iterator
->found_user_name
= strdup(membership_data
.user_name
);
315 if (!iterator
->found_user_name
) {
320 iterator
->found_group_name
= strdup(membership_data
.group_name
);
321 if (!iterator
->found_group_name
) {
328 if (FLAGS_SET(flags
, VARLINK_REPLY_CONTINUES
))
336 assert_not_reached();
340 /* If we got one ESRCH, let that win. This way when we do a wild dump we won't be tripped up by bad
341 * errors if at least one connection ended cleanly */
342 if (r
== -ESRCH
|| iterator
->error
== 0)
343 iterator
->error
= -r
;
345 assert_se(set_remove(iterator
->links
, link
) == link
);
346 link
= varlink_unref(link
);
350 static int userdb_connect(
351 UserDBIterator
*iterator
,
355 JsonVariant
*query
) {
357 _cleanup_(varlink_unrefp
) Varlink
*vl
= NULL
;
364 r
= varlink_connect_address(&vl
, path
);
366 return log_debug_errno(r
, "Unable to connect to %s: %m", path
);
368 varlink_set_userdata(vl
, iterator
);
370 if (!iterator
->event
) {
371 r
= sd_event_new(&iterator
->event
);
373 return log_debug_errno(r
, "Unable to allocate event loop: %m");
376 r
= varlink_attach_event(vl
, iterator
->event
, SD_EVENT_PRIORITY_NORMAL
);
378 return log_debug_errno(r
, "Failed to attach varlink connection to event loop: %m");
380 (void) varlink_set_description(vl
, path
);
382 r
= varlink_bind_reply(vl
, userdb_on_query_reply
);
384 return log_debug_errno(r
, "Failed to bind reply callback: %m");
387 r
= varlink_observe(vl
, method
, query
);
389 r
= varlink_invoke(vl
, method
, query
);
391 return log_debug_errno(r
, "Failed to invoke varlink method: %m");
393 r
= set_ensure_consume(&iterator
->links
, &link_hash_ops
, TAKE_PTR(vl
));
395 return log_debug_errno(r
, "Failed to add varlink connection to set: %m");
399 static int userdb_start_query(
400 UserDBIterator
*iterator
,
406 _cleanup_(strv_freep
) char **except
= NULL
, **only
= NULL
;
407 _cleanup_(closedirp
) DIR *d
= NULL
;
415 if (FLAGS_SET(flags
, USERDB_EXCLUDE_VARLINK
))
418 e
= getenv("SYSTEMD_BYPASS_USERDB");
420 r
= parse_boolean(e
);
424 except
= strv_split(e
, ":");
430 e
= getenv("SYSTEMD_ONLY_USERDB");
432 only
= strv_split(e
, ":");
437 /* First, let's talk to the multiplexer, if we can */
438 if ((flags
& (USERDB_AVOID_MULTIPLEXER
|USERDB_EXCLUDE_DYNAMIC_USER
|USERDB_EXCLUDE_NSS
|USERDB_EXCLUDE_DROPIN
|USERDB_DONT_SYNTHESIZE
)) == 0 &&
439 !strv_contains(except
, "io.systemd.Multiplexer") &&
440 (!only
|| strv_contains(only
, "io.systemd.Multiplexer"))) {
441 _cleanup_(json_variant_unrefp
) JsonVariant
*patched_query
= json_variant_ref(query
);
443 r
= json_variant_set_field_string(&patched_query
, "service", "io.systemd.Multiplexer");
445 return log_debug_errno(r
, "Unable to set service JSON field: %m");
447 r
= userdb_connect(iterator
, "/run/systemd/userdb/io.systemd.Multiplexer", method
, more
, patched_query
);
449 iterator
->nss_covered
= true; /* The multiplexer does NSS */
450 iterator
->dropin_covered
= true; /* It also handles drop-in stuff */
455 d
= opendir("/run/systemd/userdb/");
463 FOREACH_DIRENT(de
, d
, return -errno
) {
464 _cleanup_(json_variant_unrefp
) JsonVariant
*patched_query
= NULL
;
465 _cleanup_free_
char *p
= NULL
;
466 bool is_nss
, is_dropin
;
468 if (streq(de
->d_name
, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
471 if (FLAGS_SET(flags
, USERDB_EXCLUDE_DYNAMIC_USER
) &&
472 streq(de
->d_name
, "io.systemd.DynamicUser"))
475 /* Avoid NSS is this is requested. Note that we also skip NSS when we were asked to skip the
476 * multiplexer, since in that case it's safer to do NSS in the client side emulation below
477 * (and when we run as part of systemd-userdbd.service we don't want to talk to ourselves
479 is_nss
= streq(de
->d_name
, "io.systemd.NameServiceSwitch");
480 if ((flags
& (USERDB_EXCLUDE_NSS
|USERDB_AVOID_MULTIPLEXER
)) && is_nss
)
483 /* Similar for the drop-in service */
484 is_dropin
= streq(de
->d_name
, "io.systemd.DropIn");
485 if ((flags
& (USERDB_EXCLUDE_DROPIN
|USERDB_AVOID_MULTIPLEXER
)) && is_dropin
)
488 if (strv_contains(except
, de
->d_name
))
491 if (only
&& !strv_contains(only
, de
->d_name
))
494 p
= path_join("/run/systemd/userdb/", de
->d_name
);
498 patched_query
= json_variant_ref(query
);
499 r
= json_variant_set_field_string(&patched_query
, "service", de
->d_name
);
501 return log_debug_errno(r
, "Unable to set service JSON field: %m");
503 r
= userdb_connect(iterator
, p
, method
, more
, patched_query
);
504 if (is_nss
&& r
>= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
505 * and could connect to it */
506 iterator
->nss_covered
= true;
507 if (is_dropin
&& r
>= 0)
508 iterator
->dropin_covered
= true;
510 if (ret
== 0 && r
< 0)
514 if (set_isempty(iterator
->links
))
515 return ret
< 0 ? ret
: -ESRCH
; /* propagate last error we saw if we couldn't connect to anything. */
517 /* We connected to some services, in this case, ignore the ones we failed on */
521 static int userdb_process(
522 UserDBIterator
*iterator
,
523 UserRecord
**ret_user_record
,
524 GroupRecord
**ret_group_record
,
525 char **ret_user_name
,
526 char **ret_group_name
) {
533 if (iterator
->what
== LOOKUP_USER
&& iterator
->found_user
) {
535 *ret_user_record
= TAKE_PTR(iterator
->found_user
);
537 iterator
->found_user
= user_record_unref(iterator
->found_user
);
539 if (ret_group_record
)
540 *ret_group_record
= NULL
;
542 *ret_user_name
= NULL
;
544 *ret_group_name
= NULL
;
549 if (iterator
->what
== LOOKUP_GROUP
&& iterator
->found_group
) {
550 if (ret_group_record
)
551 *ret_group_record
= TAKE_PTR(iterator
->found_group
);
553 iterator
->found_group
= group_record_unref(iterator
->found_group
);
556 *ret_user_record
= NULL
;
558 *ret_user_name
= NULL
;
560 *ret_group_name
= NULL
;
565 if (iterator
->what
== LOOKUP_MEMBERSHIP
&& iterator
->found_user_name
&& iterator
->found_group_name
) {
567 *ret_user_name
= TAKE_PTR(iterator
->found_user_name
);
569 iterator
->found_user_name
= mfree(iterator
->found_user_name
);
572 *ret_group_name
= TAKE_PTR(iterator
->found_group_name
);
574 iterator
->found_group_name
= mfree(iterator
->found_group_name
);
577 *ret_user_record
= NULL
;
578 if (ret_group_record
)
579 *ret_group_record
= NULL
;
584 if (set_isempty(iterator
->links
)) {
585 if (iterator
->error
== 0)
588 return -abs(iterator
->error
);
591 if (!iterator
->event
)
594 r
= sd_event_run(iterator
->event
, UINT64_MAX
);
600 static int synthetic_root_user_build(UserRecord
**ret
) {
601 return user_record_build(
603 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING("root")),
604 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(0)),
605 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
606 JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_STRING("/root")),
607 JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("intrinsic"))));
610 static int synthetic_nobody_user_build(UserRecord
**ret
) {
611 return user_record_build(
613 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(NOBODY_USER_NAME
)),
614 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(UID_NOBODY
)),
615 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY
)),
616 JSON_BUILD_PAIR("shell", JSON_BUILD_STRING(NOLOGIN
)),
617 JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
618 JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("intrinsic"))));
621 int userdb_by_name(const char *name
, UserDBFlags flags
, UserRecord
**ret
) {
622 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
623 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
626 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
629 r
= json_build(&query
, JSON_BUILD_OBJECT(
630 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name
))));
634 iterator
= userdb_iterator_new(LOOKUP_USER
, flags
);
638 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetUserRecord", false, query
, flags
);
640 r
= userdb_process(iterator
, ret
, NULL
, NULL
, NULL
);
645 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !iterator
->dropin_covered
) {
646 r
= dropin_user_record_by_name(name
, NULL
, flags
, ret
);
651 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !iterator
->nss_covered
) {
652 /* Make sure the NSS lookup doesn't recurse back to us. */
654 r
= userdb_iterator_block_nss_systemd(iterator
);
656 /* Client-side NSS fallback */
657 r
= nss_user_record_by_name(name
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
663 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
664 if (streq(name
, "root"))
665 return synthetic_root_user_build(ret
);
667 if (streq(name
, NOBODY_USER_NAME
) && synthesize_nobody())
668 return synthetic_nobody_user_build(ret
);
674 int userdb_by_uid(uid_t uid
, UserDBFlags flags
, UserRecord
**ret
) {
675 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
676 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
679 if (!uid_is_valid(uid
))
682 r
= json_build(&query
, JSON_BUILD_OBJECT(
683 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid
))));
687 iterator
= userdb_iterator_new(LOOKUP_USER
, flags
);
691 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetUserRecord", false, query
, flags
);
693 r
= userdb_process(iterator
, ret
, NULL
, NULL
, NULL
);
698 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !iterator
->dropin_covered
) {
699 r
= dropin_user_record_by_uid(uid
, NULL
, flags
, ret
);
704 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !iterator
->nss_covered
) {
705 r
= userdb_iterator_block_nss_systemd(iterator
);
707 /* Client-side NSS fallback */
708 r
= nss_user_record_by_uid(uid
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
714 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
716 return synthetic_root_user_build(ret
);
718 if (uid
== UID_NOBODY
&& synthesize_nobody())
719 return synthetic_nobody_user_build(ret
);
725 int userdb_all(UserDBFlags flags
, UserDBIterator
**ret
) {
726 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
731 iterator
= userdb_iterator_new(LOOKUP_USER
, flags
);
735 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetUserRecord", true, NULL
, flags
);
737 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
738 r
= userdb_iterator_block_nss_systemd(iterator
);
743 iterator
->nss_iterating
= true;
746 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
)) {
747 r
= conf_files_list_nulstr(
751 CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
,
752 USERDB_DROPIN_DIR_NULSTR("userdb"));
754 log_debug_errno(r
, "Failed to find user drop-ins, ignoring: %m");
757 /* propagate IPC error, but only if there are no drop-ins */
759 !iterator
->nss_iterating
&&
760 strv_isempty(iterator
->dropins
))
763 *ret
= TAKE_PTR(iterator
);
767 int userdb_iterator_get(UserDBIterator
*iterator
, UserRecord
**ret
) {
771 assert(iterator
->what
== LOOKUP_USER
);
773 if (iterator
->nss_iterating
) {
776 /* If NSS isn't covered elsewhere, let's iterate through it first, since it probably contains
777 * the more traditional sources, which are probably good to show first. */
781 _cleanup_free_
char *buffer
= NULL
;
782 bool incomplete
= false;
785 if (streq_ptr(pw
->pw_name
, "root"))
786 iterator
->synthesize_root
= false;
787 if (pw
->pw_uid
== UID_NOBODY
)
788 iterator
->synthesize_nobody
= false;
790 if (!FLAGS_SET(iterator
->flags
, USERDB_SUPPRESS_SHADOW
)) {
791 r
= nss_spwd_for_passwd(pw
, &spwd
, &buffer
);
793 log_debug_errno(r
, "Failed to acquire shadow entry for user %s, ignoring: %m", pw
->pw_name
);
794 incomplete
= ERRNO_IS_PRIVILEGE(r
);
801 r
= nss_passwd_to_user_record(pw
, r
>= 0 ? &spwd
: NULL
, ret
);
806 (*ret
)->incomplete
= incomplete
;
813 log_debug_errno(errno
, "Failure to iterate NSS user database, ignoring: %m");
815 iterator
->nss_iterating
= false;
819 for (; iterator
->dropins
&& iterator
->dropins
[iterator
->current_dropin
]; iterator
->current_dropin
++) {
820 const char *i
= iterator
->dropins
[iterator
->current_dropin
];
821 _cleanup_free_
char *fn
= NULL
;
825 /* Next, let's add in the static drop-ins, which are quick to retrieve */
827 r
= path_extract_filename(i
, &fn
);
831 e
= endswith(fn
, ".user"); /* not actually a .user file? Then skip to next */
835 *e
= 0; /* Chop off suffix */
837 if (parse_uid(fn
, &uid
) < 0) /* not a UID .user file? Then skip to next */
840 r
= dropin_user_record_by_uid(uid
, i
, iterator
->flags
, ret
);
842 log_debug_errno(r
, "Failed to parse user record for UID " UID_FMT
", ignoring: %m", uid
);
843 continue; /* If we failed to parse this record, let's suppress it from enumeration,
844 * and continue with the next record. Maybe someone is dropping it files
845 * and only partially wrote this one. */
848 iterator
->current_dropin
++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
853 /* Then, let's return the users provided by varlink IPC */
854 r
= userdb_process(iterator
, ret
, NULL
, NULL
, NULL
);
857 /* Finally, synthesize root + nobody if not done yet */
858 if (iterator
->synthesize_root
) {
859 iterator
->synthesize_root
= false;
861 return synthetic_root_user_build(ret
);
864 if (iterator
->synthesize_nobody
) {
865 iterator
->synthesize_nobody
= false;
867 return synthetic_nobody_user_build(ret
);
870 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
871 if (iterator
->n_found
> 0)
878 static int synthetic_root_group_build(GroupRecord
**ret
) {
879 return group_record_build(
881 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING("root")),
882 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
883 JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("intrinsic"))));
886 static int synthetic_nobody_group_build(GroupRecord
**ret
) {
887 return group_record_build(
889 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(NOBODY_GROUP_NAME
)),
890 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY
)),
891 JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("intrinsic"))));
894 int groupdb_by_name(const char *name
, UserDBFlags flags
, GroupRecord
**ret
) {
895 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
896 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
899 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
902 r
= json_build(&query
, JSON_BUILD_OBJECT(
903 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name
))));
907 iterator
= userdb_iterator_new(LOOKUP_GROUP
, flags
);
911 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetGroupRecord", false, query
, flags
);
913 r
= userdb_process(iterator
, NULL
, ret
, NULL
, NULL
);
918 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !(iterator
&& iterator
->dropin_covered
)) {
919 r
= dropin_group_record_by_name(name
, NULL
, flags
, ret
);
925 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !(iterator
&& iterator
->nss_covered
)) {
926 r
= userdb_iterator_block_nss_systemd(iterator
);
928 r
= nss_group_record_by_name(name
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
934 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
935 if (streq(name
, "root"))
936 return synthetic_root_group_build(ret
);
938 if (streq(name
, NOBODY_GROUP_NAME
) && synthesize_nobody())
939 return synthetic_nobody_group_build(ret
);
945 int groupdb_by_gid(gid_t gid
, UserDBFlags flags
, GroupRecord
**ret
) {
946 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
947 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
950 if (!gid_is_valid(gid
))
953 r
= json_build(&query
, JSON_BUILD_OBJECT(
954 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid
))));
958 iterator
= userdb_iterator_new(LOOKUP_GROUP
, flags
);
962 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetGroupRecord", false, query
, flags
);
964 r
= userdb_process(iterator
, NULL
, ret
, NULL
, NULL
);
969 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !(iterator
&& iterator
->dropin_covered
)) {
970 r
= dropin_group_record_by_gid(gid
, NULL
, flags
, ret
);
975 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !(iterator
&& iterator
->nss_covered
)) {
976 r
= userdb_iterator_block_nss_systemd(iterator
);
978 r
= nss_group_record_by_gid(gid
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
984 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
986 return synthetic_root_group_build(ret
);
988 if (gid
== GID_NOBODY
&& synthesize_nobody())
989 return synthetic_nobody_group_build(ret
);
995 int groupdb_all(UserDBFlags flags
, UserDBIterator
**ret
) {
996 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1001 iterator
= userdb_iterator_new(LOOKUP_GROUP
, flags
);
1005 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetGroupRecord", true, NULL
, flags
);
1007 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
1008 r
= userdb_iterator_block_nss_systemd(iterator
);
1013 iterator
->nss_iterating
= true;
1016 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
)) {
1017 r
= conf_files_list_nulstr(
1021 CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
,
1022 USERDB_DROPIN_DIR_NULSTR("userdb"));
1024 log_debug_errno(r
, "Failed to find group drop-ins, ignoring: %m");
1028 !iterator
->nss_iterating
&&
1029 strv_isempty(iterator
->dropins
))
1032 *ret
= TAKE_PTR(iterator
);
1036 int groupdb_iterator_get(UserDBIterator
*iterator
, GroupRecord
**ret
) {
1040 assert(iterator
->what
== LOOKUP_GROUP
);
1042 if (iterator
->nss_iterating
) {
1048 _cleanup_free_
char *buffer
= NULL
;
1049 bool incomplete
= false;
1052 if (streq_ptr(gr
->gr_name
, "root"))
1053 iterator
->synthesize_root
= false;
1054 if (gr
->gr_gid
== GID_NOBODY
)
1055 iterator
->synthesize_nobody
= false;
1057 if (!FLAGS_SET(iterator
->flags
, USERDB_SUPPRESS_SHADOW
)) {
1058 r
= nss_sgrp_for_group(gr
, &sgrp
, &buffer
);
1060 log_debug_errno(r
, "Failed to acquire shadow entry for group %s, ignoring: %m", gr
->gr_name
);
1061 incomplete
= ERRNO_IS_PRIVILEGE(r
);
1068 r
= nss_group_to_group_record(gr
, r
>= 0 ? &sgrp
: NULL
, ret
);
1073 (*ret
)->incomplete
= incomplete
;
1075 iterator
->n_found
++;
1080 log_debug_errno(errno
, "Failure to iterate NSS group database, ignoring: %m");
1082 iterator
->nss_iterating
= false;
1086 for (; iterator
->dropins
&& iterator
->dropins
[iterator
->current_dropin
]; iterator
->current_dropin
++) {
1087 const char *i
= iterator
->dropins
[iterator
->current_dropin
];
1088 _cleanup_free_
char *fn
= NULL
;
1092 r
= path_extract_filename(i
, &fn
);
1096 e
= endswith(fn
, ".group");
1100 *e
= 0; /* Chop off suffix */
1102 if (parse_gid(fn
, &gid
) < 0)
1105 r
= dropin_group_record_by_gid(gid
, i
, iterator
->flags
, ret
);
1107 log_debug_errno(r
, "Failed to parse group record for GID " GID_FMT
", ignoring: %m", gid
);
1111 iterator
->current_dropin
++;
1112 iterator
->n_found
++;
1116 r
= userdb_process(iterator
, NULL
, ret
, NULL
, NULL
);
1118 if (iterator
->synthesize_root
) {
1119 iterator
->synthesize_root
= false;
1120 iterator
->n_found
++;
1121 return synthetic_root_group_build(ret
);
1124 if (iterator
->synthesize_nobody
) {
1125 iterator
->synthesize_nobody
= false;
1126 iterator
->n_found
++;
1127 return synthetic_nobody_group_build(ret
);
1130 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1131 if (iterator
->n_found
> 0)
1138 static void discover_membership_dropins(UserDBIterator
*i
, UserDBFlags flags
) {
1141 r
= conf_files_list_nulstr(
1145 CONF_FILES_REGULAR
|CONF_FILES_BASENAME
|CONF_FILES_FILTER_MASKED
,
1146 USERDB_DROPIN_DIR_NULSTR("userdb"));
1148 log_debug_errno(r
, "Failed to find membership drop-ins, ignoring: %m");
1151 int membershipdb_by_user(const char *name
, UserDBFlags flags
, UserDBIterator
**ret
) {
1152 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1153 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
1158 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
1161 r
= json_build(&query
, JSON_BUILD_OBJECT(
1162 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name
))));
1166 iterator
= userdb_iterator_new(LOOKUP_MEMBERSHIP
, flags
);
1170 iterator
->filter_user_name
= strdup(name
);
1171 if (!iterator
->filter_user_name
)
1174 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetMemberships", true, query
, flags
);
1176 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
1177 r
= userdb_iterator_block_nss_systemd(iterator
);
1182 iterator
->nss_iterating
= true;
1185 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
))
1186 discover_membership_dropins(iterator
, flags
);
1189 !iterator
->nss_iterating
&&
1190 strv_isempty(iterator
->dropins
))
1193 *ret
= TAKE_PTR(iterator
);
1197 int membershipdb_by_group(const char *name
, UserDBFlags flags
, UserDBIterator
**ret
) {
1198 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1199 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
1204 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
1207 r
= json_build(&query
, JSON_BUILD_OBJECT(
1208 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name
))));
1212 iterator
= userdb_iterator_new(LOOKUP_MEMBERSHIP
, flags
);
1216 iterator
->filter_group_name
= strdup(name
);
1217 if (!iterator
->filter_group_name
)
1220 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetMemberships", true, query
, flags
);
1222 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
1223 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
1225 r
= userdb_iterator_block_nss_systemd(iterator
);
1229 /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
1230 (void) nss_group_record_by_name(name
, false, &gr
);
1232 iterator
->members_of_group
= strv_copy(gr
->members
);
1233 if (!iterator
->members_of_group
)
1236 iterator
->index_members_of_group
= 0;
1238 iterator
->found_group_name
= strdup(name
);
1239 if (!iterator
->found_group_name
)
1244 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
))
1245 discover_membership_dropins(iterator
, flags
);
1248 strv_isempty(iterator
->members_of_group
) &&
1249 strv_isempty(iterator
->dropins
))
1252 *ret
= TAKE_PTR(iterator
);
1256 int membershipdb_all(UserDBFlags flags
, UserDBIterator
**ret
) {
1257 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1262 iterator
= userdb_iterator_new(LOOKUP_MEMBERSHIP
, flags
);
1266 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetMemberships", true, NULL
, flags
);
1268 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
1269 r
= userdb_iterator_block_nss_systemd(iterator
);
1274 iterator
->nss_iterating
= true;
1277 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
))
1278 discover_membership_dropins(iterator
, flags
);
1281 !iterator
->nss_iterating
&&
1282 strv_isempty(iterator
->dropins
))
1285 *ret
= TAKE_PTR(iterator
);
1289 int membershipdb_iterator_get(
1290 UserDBIterator
*iterator
,
1299 /* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
1300 if (!iterator
->members_of_group
) {
1303 if (!iterator
->nss_iterating
)
1306 assert(!iterator
->found_user_name
);
1312 log_debug_errno(errno
, "Failure during NSS group iteration, ignoring: %m");
1316 } while (iterator
->filter_user_name
? !strv_contains(g
->gr_mem
, iterator
->filter_user_name
) :
1317 strv_isempty(g
->gr_mem
));
1320 r
= free_and_strdup(&iterator
->found_group_name
, g
->gr_name
);
1324 if (iterator
->filter_user_name
)
1325 iterator
->members_of_group
= strv_new(iterator
->filter_user_name
);
1327 iterator
->members_of_group
= strv_copy(g
->gr_mem
);
1328 if (!iterator
->members_of_group
)
1331 iterator
->index_members_of_group
= 0;
1333 iterator
->nss_iterating
= false;
1339 assert(iterator
->found_group_name
);
1340 assert(iterator
->members_of_group
);
1341 assert(!iterator
->found_user_name
);
1343 if (iterator
->members_of_group
[iterator
->index_members_of_group
]) {
1344 _cleanup_free_
char *cu
= NULL
, *cg
= NULL
;
1347 cu
= strdup(iterator
->members_of_group
[iterator
->index_members_of_group
]);
1353 cg
= strdup(iterator
->found_group_name
);
1359 *ret_user
= TAKE_PTR(cu
);
1362 *ret_group
= TAKE_PTR(cg
);
1364 iterator
->index_members_of_group
++;
1368 iterator
->members_of_group
= strv_free(iterator
->members_of_group
);
1369 iterator
->found_group_name
= mfree(iterator
->found_group_name
);
1372 for (; iterator
->dropins
&& iterator
->dropins
[iterator
->current_dropin
]; iterator
->current_dropin
++) {
1373 const char *i
= iterator
->dropins
[iterator
->current_dropin
], *e
, *c
;
1374 _cleanup_free_
char *un
= NULL
, *gn
= NULL
;
1376 e
= endswith(i
, ".membership");
1380 c
= memchr(i
, ':', e
- i
);
1384 un
= strndup(i
, c
- i
);
1387 if (iterator
->filter_user_name
) {
1388 if (!streq(un
, iterator
->filter_user_name
))
1390 } else if (!valid_user_group_name(un
, VALID_USER_RELAX
))
1393 c
++; /* skip over ':' */
1394 gn
= strndup(c
, e
- c
);
1397 if (iterator
->filter_group_name
) {
1398 if (!streq(gn
, iterator
->filter_group_name
))
1400 } else if (!valid_user_group_name(gn
, VALID_USER_RELAX
))
1403 iterator
->current_dropin
++;
1404 iterator
->n_found
++;
1407 *ret_user
= TAKE_PTR(un
);
1409 *ret_group
= TAKE_PTR(gn
);
1414 r
= userdb_process(iterator
, NULL
, NULL
, ret_user
, ret_group
);
1415 if (r
< 0 && iterator
->n_found
> 0)
1421 int membershipdb_by_group_strv(const char *name
, UserDBFlags flags
, char ***ret
) {
1422 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1423 _cleanup_strv_free_
char **members
= NULL
;
1429 r
= membershipdb_by_group(name
, flags
, &iterator
);
1434 _cleanup_free_
char *user_name
= NULL
;
1436 r
= membershipdb_iterator_get(iterator
, &user_name
, NULL
);
1442 r
= strv_consume(&members
, TAKE_PTR(user_name
));
1450 *ret
= TAKE_PTR(members
);
1454 int userdb_block_nss_systemd(int b
) {
1455 _cleanup_(dlclosep
) void *dl
= NULL
;
1456 int (*call
)(bool b
);
1458 /* Note that we might be called from libnss_systemd.so.2 itself, but that should be fine, really. */
1460 dl
= dlopen(ROOTLIBDIR
"/libnss_systemd.so.2", RTLD_LAZY
|RTLD_NODELETE
);
1462 /* If the file isn't installed, don't complain loudly */
1463 log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
1467 call
= (int (*)(bool b
)) dlsym(dl
, "_nss_systemd_block");
1469 /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
1470 return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD
),
1471 "Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());