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
= ASSERT_PTR(userdata
);
160 log_debug("Got lookup error: %s", error_id
);
162 if (STR_IN_SET(error_id
,
163 "io.systemd.UserDatabase.NoRecordFound",
164 "io.systemd.UserDatabase.ConflictingRecordFound"))
166 else if (streq(error_id
, "io.systemd.UserDatabase.ServiceNotAvailable"))
168 else if (streq(error_id
, "io.systemd.UserDatabase.EnumerationNotSupported"))
170 else if (streq(error_id
, VARLINK_ERROR_TIMEOUT
))
178 switch (iterator
->what
) {
181 _cleanup_(user_group_data_release
) struct user_group_data user_data
= {};
183 static const JsonDispatch dispatch_table
[] = {
184 { "record", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_variant
, offsetof(struct user_group_data
, record
), 0 },
185 { "incomplete", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(struct user_group_data
, incomplete
), 0 },
188 _cleanup_(user_record_unrefp
) UserRecord
*hr
= NULL
;
190 assert_se(!iterator
->found_user
);
192 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &user_data
);
196 if (!user_data
.record
) {
197 r
= log_debug_errno(SYNTHETIC_ERRNO(EIO
), "Reply is missing record key");
201 hr
= user_record_new();
207 r
= user_record_load(hr
, user_data
.record
, USER_RECORD_LOAD_REFUSE_SECRET
|USER_RECORD_PERMISSIVE
);
212 r
= log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "User record does not carry service information, refusing.");
216 hr
->incomplete
= user_data
.incomplete
;
218 /* We match the root user by the name since the name is our primary key. We match the nobody
219 * use by UID though, since the name might differ on OSes */
220 if (streq_ptr(hr
->user_name
, "root"))
221 iterator
->synthesize_root
= false;
222 if (hr
->uid
== UID_NOBODY
)
223 iterator
->synthesize_nobody
= false;
225 iterator
->found_user
= TAKE_PTR(hr
);
228 /* More stuff coming? then let's just exit cleanly here */
229 if (FLAGS_SET(flags
, VARLINK_REPLY_CONTINUES
))
232 /* Otherwise, let's remove this link and exit cleanly then */
238 _cleanup_(user_group_data_release
) struct user_group_data group_data
= {};
240 static const JsonDispatch dispatch_table
[] = {
241 { "record", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_variant
, offsetof(struct user_group_data
, record
), 0 },
242 { "incomplete", JSON_VARIANT_BOOLEAN
, json_dispatch_boolean
, offsetof(struct user_group_data
, incomplete
), 0 },
245 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
247 assert_se(!iterator
->found_group
);
249 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &group_data
);
253 if (!group_data
.record
) {
254 r
= log_debug_errno(SYNTHETIC_ERRNO(EIO
), "Reply is missing record key");
258 g
= group_record_new();
264 r
= group_record_load(g
, group_data
.record
, USER_RECORD_LOAD_REFUSE_SECRET
|USER_RECORD_PERMISSIVE
);
269 r
= log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Group record does not carry service information, refusing.");
273 g
->incomplete
= group_data
.incomplete
;
275 if (streq_ptr(g
->group_name
, "root"))
276 iterator
->synthesize_root
= false;
277 if (g
->gid
== GID_NOBODY
)
278 iterator
->synthesize_nobody
= false;
280 iterator
->found_group
= TAKE_PTR(g
);
283 if (FLAGS_SET(flags
, VARLINK_REPLY_CONTINUES
))
290 case LOOKUP_MEMBERSHIP
: {
291 struct membership_data
{
292 const char *user_name
;
293 const char *group_name
;
294 } membership_data
= {};
296 static const JsonDispatch dispatch_table
[] = {
297 { "userName", JSON_VARIANT_STRING
, json_dispatch_user_group_name
, offsetof(struct membership_data
, user_name
), JSON_RELAX
},
298 { "groupName", JSON_VARIANT_STRING
, json_dispatch_user_group_name
, offsetof(struct membership_data
, group_name
), JSON_RELAX
},
302 assert(!iterator
->found_user_name
);
303 assert(!iterator
->found_group_name
);
305 r
= json_dispatch(parameters
, dispatch_table
, NULL
, 0, &membership_data
);
309 iterator
->found_user_name
= mfree(iterator
->found_user_name
);
310 iterator
->found_group_name
= mfree(iterator
->found_group_name
);
312 iterator
->found_user_name
= strdup(membership_data
.user_name
);
313 if (!iterator
->found_user_name
) {
318 iterator
->found_group_name
= strdup(membership_data
.group_name
);
319 if (!iterator
->found_group_name
) {
326 if (FLAGS_SET(flags
, VARLINK_REPLY_CONTINUES
))
334 assert_not_reached();
338 /* If we got one ESRCH, let that win. This way when we do a wild dump we won't be tripped up by bad
339 * errors if at least one connection ended cleanly */
340 if (r
== -ESRCH
|| iterator
->error
== 0)
341 iterator
->error
= -r
;
343 assert_se(set_remove(iterator
->links
, link
) == link
);
344 link
= varlink_unref(link
);
348 static int userdb_connect(
349 UserDBIterator
*iterator
,
353 JsonVariant
*query
) {
355 _cleanup_(varlink_unrefp
) Varlink
*vl
= NULL
;
362 r
= varlink_connect_address(&vl
, path
);
364 return log_debug_errno(r
, "Unable to connect to %s: %m", path
);
366 varlink_set_userdata(vl
, iterator
);
368 if (!iterator
->event
) {
369 r
= sd_event_new(&iterator
->event
);
371 return log_debug_errno(r
, "Unable to allocate event loop: %m");
374 r
= varlink_attach_event(vl
, iterator
->event
, SD_EVENT_PRIORITY_NORMAL
);
376 return log_debug_errno(r
, "Failed to attach varlink connection to event loop: %m");
378 (void) varlink_set_description(vl
, path
);
380 r
= varlink_bind_reply(vl
, userdb_on_query_reply
);
382 return log_debug_errno(r
, "Failed to bind reply callback: %m");
385 r
= varlink_observe(vl
, method
, query
);
387 r
= varlink_invoke(vl
, method
, query
);
389 return log_debug_errno(r
, "Failed to invoke varlink method: %m");
391 r
= set_ensure_consume(&iterator
->links
, &link_hash_ops
, TAKE_PTR(vl
));
393 return log_debug_errno(r
, "Failed to add varlink connection to set: %m");
397 static int userdb_start_query(
398 UserDBIterator
*iterator
,
404 _cleanup_(strv_freep
) char **except
= NULL
, **only
= NULL
;
405 _cleanup_(closedirp
) DIR *d
= NULL
;
412 if (FLAGS_SET(flags
, USERDB_EXCLUDE_VARLINK
))
415 e
= getenv("SYSTEMD_BYPASS_USERDB");
417 r
= parse_boolean(e
);
421 except
= strv_split(e
, ":");
427 e
= getenv("SYSTEMD_ONLY_USERDB");
429 only
= strv_split(e
, ":");
434 /* First, let's talk to the multiplexer, if we can */
435 if ((flags
& (USERDB_AVOID_MULTIPLEXER
|USERDB_EXCLUDE_DYNAMIC_USER
|USERDB_EXCLUDE_NSS
|USERDB_EXCLUDE_DROPIN
|USERDB_DONT_SYNTHESIZE
)) == 0 &&
436 !strv_contains(except
, "io.systemd.Multiplexer") &&
437 (!only
|| strv_contains(only
, "io.systemd.Multiplexer"))) {
438 _cleanup_(json_variant_unrefp
) JsonVariant
*patched_query
= json_variant_ref(query
);
440 r
= json_variant_set_field_string(&patched_query
, "service", "io.systemd.Multiplexer");
442 return log_debug_errno(r
, "Unable to set service JSON field: %m");
444 r
= userdb_connect(iterator
, "/run/systemd/userdb/io.systemd.Multiplexer", method
, more
, patched_query
);
446 iterator
->nss_covered
= true; /* The multiplexer does NSS */
447 iterator
->dropin_covered
= true; /* It also handles drop-in stuff */
452 d
= opendir("/run/systemd/userdb/");
460 FOREACH_DIRENT(de
, d
, return -errno
) {
461 _cleanup_(json_variant_unrefp
) JsonVariant
*patched_query
= NULL
;
462 _cleanup_free_
char *p
= NULL
;
463 bool is_nss
, is_dropin
;
465 if (streq(de
->d_name
, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
468 if (FLAGS_SET(flags
, USERDB_EXCLUDE_DYNAMIC_USER
) &&
469 streq(de
->d_name
, "io.systemd.DynamicUser"))
472 /* Avoid NSS if this is requested. Note that we also skip NSS when we were asked to skip the
473 * multiplexer, since in that case it's safer to do NSS in the client side emulation below
474 * (and when we run as part of systemd-userdbd.service we don't want to talk to ourselves
476 is_nss
= streq(de
->d_name
, "io.systemd.NameServiceSwitch");
477 if ((flags
& (USERDB_EXCLUDE_NSS
|USERDB_AVOID_MULTIPLEXER
)) && is_nss
)
480 /* Similar for the drop-in service */
481 is_dropin
= streq(de
->d_name
, "io.systemd.DropIn");
482 if ((flags
& (USERDB_EXCLUDE_DROPIN
|USERDB_AVOID_MULTIPLEXER
)) && is_dropin
)
485 if (strv_contains(except
, de
->d_name
))
488 if (only
&& !strv_contains(only
, de
->d_name
))
491 p
= path_join("/run/systemd/userdb/", de
->d_name
);
495 patched_query
= json_variant_ref(query
);
496 r
= json_variant_set_field_string(&patched_query
, "service", de
->d_name
);
498 return log_debug_errno(r
, "Unable to set service JSON field: %m");
500 r
= userdb_connect(iterator
, p
, method
, more
, patched_query
);
501 if (is_nss
&& r
>= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
502 * and could connect to it */
503 iterator
->nss_covered
= true;
504 if (is_dropin
&& r
>= 0)
505 iterator
->dropin_covered
= true;
507 if (ret
== 0 && r
< 0)
511 if (set_isempty(iterator
->links
))
512 return ret
< 0 ? ret
: -ESRCH
; /* propagate last error we saw if we couldn't connect to anything. */
514 /* We connected to some services, in this case, ignore the ones we failed on */
518 static int userdb_process(
519 UserDBIterator
*iterator
,
520 UserRecord
**ret_user_record
,
521 GroupRecord
**ret_group_record
,
522 char **ret_user_name
,
523 char **ret_group_name
) {
530 if (iterator
->what
== LOOKUP_USER
&& iterator
->found_user
) {
532 *ret_user_record
= TAKE_PTR(iterator
->found_user
);
534 iterator
->found_user
= user_record_unref(iterator
->found_user
);
536 if (ret_group_record
)
537 *ret_group_record
= NULL
;
539 *ret_user_name
= NULL
;
541 *ret_group_name
= NULL
;
546 if (iterator
->what
== LOOKUP_GROUP
&& iterator
->found_group
) {
547 if (ret_group_record
)
548 *ret_group_record
= TAKE_PTR(iterator
->found_group
);
550 iterator
->found_group
= group_record_unref(iterator
->found_group
);
553 *ret_user_record
= NULL
;
555 *ret_user_name
= NULL
;
557 *ret_group_name
= NULL
;
562 if (iterator
->what
== LOOKUP_MEMBERSHIP
&& iterator
->found_user_name
&& iterator
->found_group_name
) {
564 *ret_user_name
= TAKE_PTR(iterator
->found_user_name
);
566 iterator
->found_user_name
= mfree(iterator
->found_user_name
);
569 *ret_group_name
= TAKE_PTR(iterator
->found_group_name
);
571 iterator
->found_group_name
= mfree(iterator
->found_group_name
);
574 *ret_user_record
= NULL
;
575 if (ret_group_record
)
576 *ret_group_record
= NULL
;
581 if (set_isempty(iterator
->links
)) {
582 if (iterator
->error
== 0)
585 return -abs(iterator
->error
);
588 if (!iterator
->event
)
591 r
= sd_event_run(iterator
->event
, UINT64_MAX
);
597 static int synthetic_root_user_build(UserRecord
**ret
) {
598 return user_record_build(
600 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING("root")),
601 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(0)),
602 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
603 JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/root")),
604 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
607 static int synthetic_nobody_user_build(UserRecord
**ret
) {
608 return user_record_build(
610 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING(NOBODY_USER_NAME
)),
611 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(UID_NOBODY
)),
612 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY
)),
613 JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN
)),
614 JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
615 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
618 int userdb_by_name(const char *name
, UserDBFlags flags
, UserRecord
**ret
) {
619 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
620 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
623 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
626 r
= json_build(&query
, JSON_BUILD_OBJECT(
627 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name
))));
631 iterator
= userdb_iterator_new(LOOKUP_USER
, flags
);
635 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetUserRecord", false, query
, flags
);
637 r
= userdb_process(iterator
, ret
, NULL
, NULL
, NULL
);
642 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !iterator
->dropin_covered
) {
643 r
= dropin_user_record_by_name(name
, NULL
, flags
, ret
);
648 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !iterator
->nss_covered
) {
649 /* Make sure the NSS lookup doesn't recurse back to us. */
651 r
= userdb_iterator_block_nss_systemd(iterator
);
653 /* Client-side NSS fallback */
654 r
= nss_user_record_by_name(name
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
660 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
661 if (streq(name
, "root"))
662 return synthetic_root_user_build(ret
);
664 if (streq(name
, NOBODY_USER_NAME
) && synthesize_nobody())
665 return synthetic_nobody_user_build(ret
);
671 int userdb_by_uid(uid_t uid
, UserDBFlags flags
, UserRecord
**ret
) {
672 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
673 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
676 if (!uid_is_valid(uid
))
679 r
= json_build(&query
, JSON_BUILD_OBJECT(
680 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid
))));
684 iterator
= userdb_iterator_new(LOOKUP_USER
, flags
);
688 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetUserRecord", false, query
, flags
);
690 r
= userdb_process(iterator
, ret
, NULL
, NULL
, NULL
);
695 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !iterator
->dropin_covered
) {
696 r
= dropin_user_record_by_uid(uid
, NULL
, flags
, ret
);
701 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !iterator
->nss_covered
) {
702 r
= userdb_iterator_block_nss_systemd(iterator
);
704 /* Client-side NSS fallback */
705 r
= nss_user_record_by_uid(uid
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
711 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
713 return synthetic_root_user_build(ret
);
715 if (uid
== UID_NOBODY
&& synthesize_nobody())
716 return synthetic_nobody_user_build(ret
);
722 int userdb_all(UserDBFlags flags
, UserDBIterator
**ret
) {
723 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
728 iterator
= userdb_iterator_new(LOOKUP_USER
, flags
);
732 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetUserRecord", true, NULL
, flags
);
734 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
735 r
= userdb_iterator_block_nss_systemd(iterator
);
740 iterator
->nss_iterating
= true;
743 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
)) {
744 r
= conf_files_list_nulstr(
748 CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
,
749 USERDB_DROPIN_DIR_NULSTR("userdb"));
751 log_debug_errno(r
, "Failed to find user drop-ins, ignoring: %m");
754 /* propagate IPC error, but only if there are no drop-ins */
756 !iterator
->nss_iterating
&&
757 strv_isempty(iterator
->dropins
))
760 *ret
= TAKE_PTR(iterator
);
764 int userdb_iterator_get(UserDBIterator
*iterator
, UserRecord
**ret
) {
768 assert(iterator
->what
== LOOKUP_USER
);
770 if (iterator
->nss_iterating
) {
773 /* If NSS isn't covered elsewhere, let's iterate through it first, since it probably contains
774 * the more traditional sources, which are probably good to show first. */
778 _cleanup_free_
char *buffer
= NULL
;
779 bool incomplete
= false;
782 if (streq_ptr(pw
->pw_name
, "root"))
783 iterator
->synthesize_root
= false;
784 if (pw
->pw_uid
== UID_NOBODY
)
785 iterator
->synthesize_nobody
= false;
787 if (!FLAGS_SET(iterator
->flags
, USERDB_SUPPRESS_SHADOW
)) {
788 r
= nss_spwd_for_passwd(pw
, &spwd
, &buffer
);
790 log_debug_errno(r
, "Failed to acquire shadow entry for user %s, ignoring: %m", pw
->pw_name
);
791 incomplete
= ERRNO_IS_PRIVILEGE(r
);
798 r
= nss_passwd_to_user_record(pw
, r
>= 0 ? &spwd
: NULL
, ret
);
803 (*ret
)->incomplete
= incomplete
;
810 log_debug_errno(errno
, "Failure to iterate NSS user database, ignoring: %m");
812 iterator
->nss_iterating
= false;
816 for (; iterator
->dropins
&& iterator
->dropins
[iterator
->current_dropin
]; iterator
->current_dropin
++) {
817 const char *i
= iterator
->dropins
[iterator
->current_dropin
];
818 _cleanup_free_
char *fn
= NULL
;
822 /* Next, let's add in the static drop-ins, which are quick to retrieve */
824 r
= path_extract_filename(i
, &fn
);
828 e
= endswith(fn
, ".user"); /* not actually a .user file? Then skip to next */
832 *e
= 0; /* Chop off suffix */
834 if (parse_uid(fn
, &uid
) < 0) /* not a UID .user file? Then skip to next */
837 r
= dropin_user_record_by_uid(uid
, i
, iterator
->flags
, ret
);
839 log_debug_errno(r
, "Failed to parse user record for UID " UID_FMT
", ignoring: %m", uid
);
840 continue; /* If we failed to parse this record, let's suppress it from enumeration,
841 * and continue with the next record. Maybe someone is dropping it files
842 * and only partially wrote this one. */
845 iterator
->current_dropin
++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
850 /* Then, let's return the users provided by varlink IPC */
851 r
= userdb_process(iterator
, ret
, NULL
, NULL
, NULL
);
854 /* Finally, synthesize root + nobody if not done yet */
855 if (iterator
->synthesize_root
) {
856 iterator
->synthesize_root
= false;
858 return synthetic_root_user_build(ret
);
861 if (iterator
->synthesize_nobody
) {
862 iterator
->synthesize_nobody
= false;
864 return synthetic_nobody_user_build(ret
);
867 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
868 if (iterator
->n_found
> 0)
875 static int synthetic_root_group_build(GroupRecord
**ret
) {
876 return group_record_build(
878 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING("root")),
879 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
880 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
883 static int synthetic_nobody_group_build(GroupRecord
**ret
) {
884 return group_record_build(
886 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING(NOBODY_GROUP_NAME
)),
887 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY
)),
888 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
891 int groupdb_by_name(const char *name
, UserDBFlags flags
, GroupRecord
**ret
) {
892 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
893 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
896 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
899 r
= json_build(&query
, JSON_BUILD_OBJECT(
900 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name
))));
904 iterator
= userdb_iterator_new(LOOKUP_GROUP
, flags
);
908 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetGroupRecord", false, query
, flags
);
910 r
= userdb_process(iterator
, NULL
, ret
, NULL
, NULL
);
915 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !(iterator
&& iterator
->dropin_covered
)) {
916 r
= dropin_group_record_by_name(name
, NULL
, flags
, ret
);
922 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !(iterator
&& iterator
->nss_covered
)) {
923 r
= userdb_iterator_block_nss_systemd(iterator
);
925 r
= nss_group_record_by_name(name
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
931 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
932 if (streq(name
, "root"))
933 return synthetic_root_group_build(ret
);
935 if (streq(name
, NOBODY_GROUP_NAME
) && synthesize_nobody())
936 return synthetic_nobody_group_build(ret
);
942 int groupdb_by_gid(gid_t gid
, UserDBFlags flags
, GroupRecord
**ret
) {
943 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
944 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
947 if (!gid_is_valid(gid
))
950 r
= json_build(&query
, JSON_BUILD_OBJECT(
951 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid
))));
955 iterator
= userdb_iterator_new(LOOKUP_GROUP
, flags
);
959 r
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetGroupRecord", false, query
, flags
);
961 r
= userdb_process(iterator
, NULL
, ret
, NULL
, NULL
);
966 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && !(iterator
&& iterator
->dropin_covered
)) {
967 r
= dropin_group_record_by_gid(gid
, NULL
, flags
, ret
);
972 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && !(iterator
&& iterator
->nss_covered
)) {
973 r
= userdb_iterator_block_nss_systemd(iterator
);
975 r
= nss_group_record_by_gid(gid
, !FLAGS_SET(flags
, USERDB_SUPPRESS_SHADOW
), ret
);
981 if (!FLAGS_SET(flags
, USERDB_DONT_SYNTHESIZE
)) {
983 return synthetic_root_group_build(ret
);
985 if (gid
== GID_NOBODY
&& synthesize_nobody())
986 return synthetic_nobody_group_build(ret
);
992 int groupdb_all(UserDBFlags flags
, UserDBIterator
**ret
) {
993 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
998 iterator
= userdb_iterator_new(LOOKUP_GROUP
, flags
);
1002 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetGroupRecord", true, NULL
, flags
);
1004 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
1005 r
= userdb_iterator_block_nss_systemd(iterator
);
1010 iterator
->nss_iterating
= true;
1013 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
)) {
1014 r
= conf_files_list_nulstr(
1018 CONF_FILES_REGULAR
|CONF_FILES_FILTER_MASKED
,
1019 USERDB_DROPIN_DIR_NULSTR("userdb"));
1021 log_debug_errno(r
, "Failed to find group drop-ins, ignoring: %m");
1025 !iterator
->nss_iterating
&&
1026 strv_isempty(iterator
->dropins
))
1029 *ret
= TAKE_PTR(iterator
);
1033 int groupdb_iterator_get(UserDBIterator
*iterator
, GroupRecord
**ret
) {
1037 assert(iterator
->what
== LOOKUP_GROUP
);
1039 if (iterator
->nss_iterating
) {
1045 _cleanup_free_
char *buffer
= NULL
;
1046 bool incomplete
= false;
1049 if (streq_ptr(gr
->gr_name
, "root"))
1050 iterator
->synthesize_root
= false;
1051 if (gr
->gr_gid
== GID_NOBODY
)
1052 iterator
->synthesize_nobody
= false;
1054 if (!FLAGS_SET(iterator
->flags
, USERDB_SUPPRESS_SHADOW
)) {
1055 r
= nss_sgrp_for_group(gr
, &sgrp
, &buffer
);
1057 log_debug_errno(r
, "Failed to acquire shadow entry for group %s, ignoring: %m", gr
->gr_name
);
1058 incomplete
= ERRNO_IS_PRIVILEGE(r
);
1065 r
= nss_group_to_group_record(gr
, r
>= 0 ? &sgrp
: NULL
, ret
);
1070 (*ret
)->incomplete
= incomplete
;
1072 iterator
->n_found
++;
1077 log_debug_errno(errno
, "Failure to iterate NSS group database, ignoring: %m");
1079 iterator
->nss_iterating
= false;
1083 for (; iterator
->dropins
&& iterator
->dropins
[iterator
->current_dropin
]; iterator
->current_dropin
++) {
1084 const char *i
= iterator
->dropins
[iterator
->current_dropin
];
1085 _cleanup_free_
char *fn
= NULL
;
1089 r
= path_extract_filename(i
, &fn
);
1093 e
= endswith(fn
, ".group");
1097 *e
= 0; /* Chop off suffix */
1099 if (parse_gid(fn
, &gid
) < 0)
1102 r
= dropin_group_record_by_gid(gid
, i
, iterator
->flags
, ret
);
1104 log_debug_errno(r
, "Failed to parse group record for GID " GID_FMT
", ignoring: %m", gid
);
1108 iterator
->current_dropin
++;
1109 iterator
->n_found
++;
1113 r
= userdb_process(iterator
, NULL
, ret
, NULL
, NULL
);
1115 if (iterator
->synthesize_root
) {
1116 iterator
->synthesize_root
= false;
1117 iterator
->n_found
++;
1118 return synthetic_root_group_build(ret
);
1121 if (iterator
->synthesize_nobody
) {
1122 iterator
->synthesize_nobody
= false;
1123 iterator
->n_found
++;
1124 return synthetic_nobody_group_build(ret
);
1127 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1128 if (iterator
->n_found
> 0)
1135 static void discover_membership_dropins(UserDBIterator
*i
, UserDBFlags flags
) {
1138 r
= conf_files_list_nulstr(
1142 CONF_FILES_REGULAR
|CONF_FILES_BASENAME
|CONF_FILES_FILTER_MASKED
,
1143 USERDB_DROPIN_DIR_NULSTR("userdb"));
1145 log_debug_errno(r
, "Failed to find membership drop-ins, ignoring: %m");
1148 int membershipdb_by_user(const char *name
, UserDBFlags flags
, UserDBIterator
**ret
) {
1149 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1150 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
1155 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
1158 r
= json_build(&query
, JSON_BUILD_OBJECT(
1159 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name
))));
1163 iterator
= userdb_iterator_new(LOOKUP_MEMBERSHIP
, flags
);
1167 iterator
->filter_user_name
= strdup(name
);
1168 if (!iterator
->filter_user_name
)
1171 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetMemberships", true, query
, flags
);
1173 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
1174 r
= userdb_iterator_block_nss_systemd(iterator
);
1179 iterator
->nss_iterating
= true;
1182 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
))
1183 discover_membership_dropins(iterator
, flags
);
1186 !iterator
->nss_iterating
&&
1187 strv_isempty(iterator
->dropins
))
1190 *ret
= TAKE_PTR(iterator
);
1194 int membershipdb_by_group(const char *name
, UserDBFlags flags
, UserDBIterator
**ret
) {
1195 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1196 _cleanup_(json_variant_unrefp
) JsonVariant
*query
= NULL
;
1201 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
1204 r
= json_build(&query
, JSON_BUILD_OBJECT(
1205 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name
))));
1209 iterator
= userdb_iterator_new(LOOKUP_MEMBERSHIP
, flags
);
1213 iterator
->filter_group_name
= strdup(name
);
1214 if (!iterator
->filter_group_name
)
1217 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetMemberships", true, query
, flags
);
1219 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
1220 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
1222 r
= userdb_iterator_block_nss_systemd(iterator
);
1226 /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
1227 (void) nss_group_record_by_name(name
, false, &gr
);
1229 iterator
->members_of_group
= strv_copy(gr
->members
);
1230 if (!iterator
->members_of_group
)
1233 iterator
->index_members_of_group
= 0;
1235 iterator
->found_group_name
= strdup(name
);
1236 if (!iterator
->found_group_name
)
1241 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
))
1242 discover_membership_dropins(iterator
, flags
);
1245 strv_isempty(iterator
->members_of_group
) &&
1246 strv_isempty(iterator
->dropins
))
1249 *ret
= TAKE_PTR(iterator
);
1253 int membershipdb_all(UserDBFlags flags
, UserDBIterator
**ret
) {
1254 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1259 iterator
= userdb_iterator_new(LOOKUP_MEMBERSHIP
, flags
);
1263 qr
= userdb_start_query(iterator
, "io.systemd.UserDatabase.GetMemberships", true, NULL
, flags
);
1265 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_NSS
) && (qr
< 0 || !iterator
->nss_covered
)) {
1266 r
= userdb_iterator_block_nss_systemd(iterator
);
1271 iterator
->nss_iterating
= true;
1274 if (!FLAGS_SET(flags
, USERDB_EXCLUDE_DROPIN
) && (qr
< 0 || !iterator
->dropin_covered
))
1275 discover_membership_dropins(iterator
, flags
);
1278 !iterator
->nss_iterating
&&
1279 strv_isempty(iterator
->dropins
))
1282 *ret
= TAKE_PTR(iterator
);
1286 int membershipdb_iterator_get(
1287 UserDBIterator
*iterator
,
1296 /* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
1297 if (!iterator
->members_of_group
) {
1300 if (!iterator
->nss_iterating
)
1303 assert(!iterator
->found_user_name
);
1309 log_debug_errno(errno
, "Failure during NSS group iteration, ignoring: %m");
1313 } while (iterator
->filter_user_name
? !strv_contains(g
->gr_mem
, iterator
->filter_user_name
) :
1314 strv_isempty(g
->gr_mem
));
1317 r
= free_and_strdup(&iterator
->found_group_name
, g
->gr_name
);
1321 if (iterator
->filter_user_name
)
1322 iterator
->members_of_group
= strv_new(iterator
->filter_user_name
);
1324 iterator
->members_of_group
= strv_copy(g
->gr_mem
);
1325 if (!iterator
->members_of_group
)
1328 iterator
->index_members_of_group
= 0;
1330 iterator
->nss_iterating
= false;
1336 assert(iterator
->found_group_name
);
1337 assert(iterator
->members_of_group
);
1338 assert(!iterator
->found_user_name
);
1340 if (iterator
->members_of_group
[iterator
->index_members_of_group
]) {
1341 _cleanup_free_
char *cu
= NULL
, *cg
= NULL
;
1344 cu
= strdup(iterator
->members_of_group
[iterator
->index_members_of_group
]);
1350 cg
= strdup(iterator
->found_group_name
);
1356 *ret_user
= TAKE_PTR(cu
);
1359 *ret_group
= TAKE_PTR(cg
);
1361 iterator
->index_members_of_group
++;
1365 iterator
->members_of_group
= strv_free(iterator
->members_of_group
);
1366 iterator
->found_group_name
= mfree(iterator
->found_group_name
);
1369 for (; iterator
->dropins
&& iterator
->dropins
[iterator
->current_dropin
]; iterator
->current_dropin
++) {
1370 const char *i
= iterator
->dropins
[iterator
->current_dropin
], *e
, *c
;
1371 _cleanup_free_
char *un
= NULL
, *gn
= NULL
;
1373 e
= endswith(i
, ".membership");
1377 c
= memchr(i
, ':', e
- i
);
1381 un
= strndup(i
, c
- i
);
1384 if (iterator
->filter_user_name
) {
1385 if (!streq(un
, iterator
->filter_user_name
))
1387 } else if (!valid_user_group_name(un
, VALID_USER_RELAX
))
1390 c
++; /* skip over ':' */
1391 gn
= strndup(c
, e
- c
);
1394 if (iterator
->filter_group_name
) {
1395 if (!streq(gn
, iterator
->filter_group_name
))
1397 } else if (!valid_user_group_name(gn
, VALID_USER_RELAX
))
1400 iterator
->current_dropin
++;
1401 iterator
->n_found
++;
1404 *ret_user
= TAKE_PTR(un
);
1406 *ret_group
= TAKE_PTR(gn
);
1411 r
= userdb_process(iterator
, NULL
, NULL
, ret_user
, ret_group
);
1412 if (r
< 0 && iterator
->n_found
> 0)
1418 int membershipdb_by_group_strv(const char *name
, UserDBFlags flags
, char ***ret
) {
1419 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
1420 _cleanup_strv_free_
char **members
= NULL
;
1426 r
= membershipdb_by_group(name
, flags
, &iterator
);
1431 _cleanup_free_
char *user_name
= NULL
;
1433 r
= membershipdb_iterator_get(iterator
, &user_name
, NULL
);
1439 r
= strv_consume(&members
, TAKE_PTR(user_name
));
1447 *ret
= TAKE_PTR(members
);
1451 int userdb_block_nss_systemd(int b
) {
1452 _cleanup_(dlclosep
) void *dl
= NULL
;
1453 int (*call
)(bool b
);
1455 /* Note that we might be called from libnss_systemd.so.2 itself, but that should be fine, really. */
1457 dl
= dlopen(ROOTLIBDIR
"/libnss_systemd.so.2", RTLD_LAZY
|RTLD_NODELETE
);
1459 /* If the file isn't installed, don't complain loudly */
1460 log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
1464 call
= (int (*)(bool b
)) dlsym(dl
, "_nss_systemd_block");
1466 /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
1467 return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD
),
1468 "Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());