1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-22 06:50:18 +03:00

userdb: move filter of user/group records to the varlink server side (#36133)

In v257 userdbctl gained support for filtering user records with fuzzy
matching and some other parameters. It was done on the client side only.
This PR adds server-side matching, by exendting the generic userdb
varlink api.

The api is generic any may have many other implementors, hence care is
taken to fallback to exclusively client side filtering in case the
service does not support the new parameters.

In fact I even opted to not actually implement server-side filtering in
any services but systemd-userdbd.service, because it's probably not too
much an optimization in relevant services (we might want to revisit this
later). By implementing it in userdbd the primary entrypoint for userdb
is however covered: the multiplexer interface which provides a single
interface for the multitude of backends. Or in other words: the
multiplexer itself supports server-side filtering even if its own
backends don't, and will hide this neatly away.

One nice side effect from not implementing server side filtering for all
our backends is that the fallback codepaths are comprehensively tested.

Note that this adds some unit tests but not new integration test for all
this, as the filtering tests for userdbctl already existed before, we
just move their implementation from the client to the server side.
This commit is contained in:
Lennart Poettering 2025-01-29 13:11:38 +01:00 committed by GitHub
commit 37cc66324c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 680 additions and 193 deletions

3
TODO
View File

@ -1634,9 +1634,6 @@ Features:
* add growvol and makevol options for /etc/crypttab, similar to
x-systemd.growfs and x-systemd-makefs.
* userdb: allow username prefix searches in varlink API, allow realname and
realname substr searches in varlink API
* userdb: allow uid/gid range checks
* userdb: allow existence checks

View File

@ -171,6 +171,10 @@ interface io.systemd.UserDatabase
method GetUserRecord(
uid : ?int,
userName : ?string,
fuzzyNames: ?[]string,
dispositionMask: ?[]string,
uidMin: ?int,
uidMax: ?int,
service : string
) -> (
record : object,
@ -180,6 +184,10 @@ method GetUserRecord(
method GetGroupRecord(
gid : ?int,
groupName : ?string,
fuzzyNames: ?[]string,
dispositionMask: ?[]string,
gidMin: ?int,
gidMax: ?int,
service : string
) -> (
record : object,
@ -199,6 +207,7 @@ error NoRecordFound()
error BadService()
error ServiceNotAvailable()
error ConflictingRecordFound()
error NonMatchingRecordFound()
error EnumerationNotSupported()
```
@ -213,6 +222,40 @@ If neither of the two parameters are set the whole user database is enumerated.
In this case the method call needs to be made with `more` set, so that multiple method call replies may be generated as
effect, each carrying one user record.
The `fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax` fields permit
*additional* filtering of the returned set of user records. The `fuzzyNames`
parameter shall be one or more strings that shall be searched for in "fuzzy"
way. What specifically this means is left for the backend to decide, but
typically this should result in substring or string proximity matching of the
primary user name, the real name of the record and possibly other fields that
carry identifying information for the user. The `dispositionMask` field shall
be one of more user record `disposition` strings. If specified only user
records matching one of the specified dispositions should be enumerated. The
`uidMin` and `uidMax` fields specify a minimum and maximum value for the UID of
returned records. Inline searching for `uid` and `userName` support for
filtering with these four additional parameters is optional, and clients are
expected to be able to do client-side filtering in case the parameters are not
supported by a service. The service should return the usual `InvalidParameter`
error for the relevant parameter if one is passed and it does not support
it. If a request is made specifying `uid` or `userName` and a suitable record
is found, but the specified filter via `fuzzyNames`, `dispositionMask`,
`uidMin`, or `uidMax` does not match, a `NonMatchingRecordFound` error should
be returned.
Or to say this differently: the *primary search keys* are
`userName`/`groupName` and `uid`/`gid` and the *secondary search filters* are
`fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax`. If no entry matching
either of the primary search keys are found `NoRecordFound()` is returned. If
one is found that matches one but not the other primary search key
`ConflictingRecordFound()` is returned. If an entry is found that matches the
primary search key, but not the secondary match filters
`NonMatchingRecordFound()` is returned. Finally, if an entry is found that
matches both the primary search keys and the secondary search filters, they are
returned as successful response. Note that both the primary search keys and the
secondary search filters are optional, it is possible to use both, use one of
the two, or the other of the two, or neither (the latter for a complete dump of
the database).
The `service` parameter is mandatory and should be set to the service name
being talked to (i.e. to the same name as the `AF_UNIX` socket path, with the
`/run/systemd/userdb/` prefix removed). This is useful to allow implementation

View File

@ -5,6 +5,7 @@
#include "sd-bus.h"
#include "ask-password-api.h"
#include "bitfield.h"
#include "build.h"
#include "bus-common-errors.h"
#include "bus-error.h"
@ -2403,36 +2404,35 @@ static int create_from_credentials(void) {
static int has_regular_user(void) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
UserDBMatch match = USERDB_MATCH_NULL;
int r;
r = userdb_all(USERDB_SUPPRESS_SHADOW, &iterator);
match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR);
r = userdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator);
if (r < 0)
return log_error_errno(r, "Failed to create user enumerator: %m");
for (;;) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
r = userdb_iterator_get(iterator, &match, /* ret= */ NULL);
if (r == -ESRCH)
return false;
if (r < 0)
return log_error_errno(r, "Failed to enumerate users: %m");
r = userdb_iterator_get(iterator, &ur);
if (r == -ESRCH)
break;
if (r < 0)
return log_error_errno(r, "Failed to enumerate users: %m");
if (user_record_disposition(ur) == USER_REGULAR)
return true;
}
return false;
return true;
}
static int acquire_group_list(char ***ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_strv_free_ char **groups = NULL;
UserDBMatch match = USERDB_MATCH_NULL;
int r;
assert(ret);
r = groupdb_all(USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, &iterator);
match.disposition_mask = INDEXES_TO_MASK(uint64_t, USER_REGULAR, USER_SYSTEM);
r = groupdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator);
if (r == -ENOLINK)
log_debug_errno(r, "No groups found. (Didn't check via Varlink.)");
else if (r == -ESRCH)
@ -2443,25 +2443,25 @@ static int acquire_group_list(char ***ret) {
for (;;) {
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
r = groupdb_iterator_get(iterator, &gr);
r = groupdb_iterator_get(iterator, &match, &gr);
if (r == -ESRCH)
break;
if (r < 0)
return log_debug_errno(r, "Failed acquire next group: %m");
if (!IN_SET(group_record_disposition(gr), USER_REGULAR, USER_SYSTEM))
continue;
if (group_record_disposition(gr) == USER_REGULAR) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
/* Filter groups here that belong to a specific user, and are named like them */
r = userdb_by_name(gr->group_name, USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, &ur);
UserDBMatch user_match = USERDB_MATCH_NULL;
user_match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR);
r = userdb_by_name(gr->group_name, &user_match, USERDB_SUPPRESS_SHADOW, &ur);
if (r < 0 && r != -ESRCH)
return log_debug_errno(r, "Failed to check if matching user exists for group '%s': %m", gr->group_name);
if (r >= 0 && ur->gid == gr->gid && user_record_disposition(ur) == USER_REGULAR)
if (r >= 0 && ur->gid == gr->gid)
continue;
}
@ -2508,7 +2508,7 @@ static int create_interactively(void) {
continue;
}
r = userdb_by_name(username, USERDB_SUPPRESS_SHADOW, /* ret= */ NULL);
r = userdb_by_name(username, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, /* ret= */ NULL);
if (r == -ESRCH)
break;
if (r < 0)
@ -2578,7 +2578,7 @@ static int create_interactively(void) {
continue;
}
r = groupdb_by_name(s, USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, /*ret=*/ NULL);
r = groupdb_by_name(s, /* match= */ NULL, USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, /*ret=*/ NULL);
if (r == -ESRCH) {
log_notice("Specified auxiliary group does not exist, try again: %s", s);
continue;

View File

@ -191,7 +191,7 @@ int manager_add_user_by_name(
assert(m);
assert(name);
r = userdb_by_name(name, USERDB_SUPPRESS_SHADOW, &ur);
r = userdb_by_name(name, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, &ur);
if (r < 0)
return r;
@ -209,7 +209,7 @@ int manager_add_user_by_uid(
assert(m);
assert(uid_is_valid(uid));
r = userdb_by_uid(uid, USERDB_SUPPRESS_SHADOW, &ur);
r = userdb_by_uid(uid, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, &ur);
if (r < 0)
return r;

View File

@ -223,7 +223,7 @@ static int acquire_user_record(
_cleanup_free_ char *formatted = NULL;
/* Request the record ourselves */
r = userdb_by_name(username, /* flags= */ 0, &ur);
r = userdb_by_name(username, /* match= */ NULL, /* flags= */ 0, &ur);
if (r < 0) {
pam_syslog_errno(handle, LOG_ERR, r, "Failed to get user record: %m");
return PAM_USER_UNKNOWN;

View File

@ -341,7 +341,7 @@ static int run(int argc, char *argv[]) {
if (streq(verb, "start")) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
r = userdb_by_name(user, USERDB_PARSE_NUMERIC|USERDB_SUPPRESS_SHADOW, &ur);
r = userdb_by_name(user, /* match= */ NULL, USERDB_PARSE_NUMERIC|USERDB_SUPPRESS_SHADOW, &ur);
if (r == -ESRCH)
return log_error_errno(r, "User '%s' does not exist: %m", user);
if (r < 0)

View File

@ -231,7 +231,7 @@ int bind_user_prepare(
_cleanup_(group_record_unrefp) GroupRecord *g = NULL, *cg = NULL;
_cleanup_free_ char *sm = NULL, *sd = NULL;
r = userdb_by_name(*n, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, &u);
r = userdb_by_name(*n, /* match= */ NULL, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, &u);
if (r < 0)
return log_error_errno(r, "Failed to resolve user '%s': %m", *n);
@ -252,7 +252,7 @@ int bind_user_prepare(
if (u->uid >= uid_shift && u->uid < uid_shift + uid_range)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID of user '%s' to map is already in container UID range, refusing.", u->user_name);
r = groupdb_by_gid(u->gid, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, &g);
r = groupdb_by_gid(u->gid, /* match= */ NULL, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, &g);
if (r < 0)
return log_error_errno(r, "Failed to resolve group of user '%s': %m", u->user_name);

View File

@ -362,13 +362,13 @@ static int uid_is_available(
if (r > 0)
return false;
r = userdb_by_uid(candidate, USERDB_AVOID_MULTIPLEXER, NULL);
r = userdb_by_uid(candidate, /* match= */ NULL, USERDB_AVOID_MULTIPLEXER, /* ret_record= */ NULL);
if (r >= 0)
return false;
if (r != -ESRCH)
return r;
r = groupdb_by_gid(candidate, USERDB_AVOID_MULTIPLEXER, NULL);
r = groupdb_by_gid(candidate, /* match= */ NULL, USERDB_AVOID_MULTIPLEXER, /* ret_record= */ NULL);
if (r >= 0)
return false;
if (r != -ESRCH)
@ -399,13 +399,13 @@ static int name_is_available(
if (!user_name)
return -ENOMEM;
r = userdb_by_name(user_name, USERDB_AVOID_MULTIPLEXER, NULL);
r = userdb_by_name(user_name, /* match= */ NULL, USERDB_AVOID_MULTIPLEXER, /* ret_record= */ NULL);
if (r >= 0)
return false;
if (r != -ESRCH)
return r;
r = groupdb_by_name(user_name, USERDB_AVOID_MULTIPLEXER, NULL);
r = groupdb_by_name(user_name, /* match= */ NULL, USERDB_AVOID_MULTIPLEXER, /* ret_record= */ NULL);
if (r >= 0)
return false;
if (r != -ESRCH)

View File

@ -619,7 +619,7 @@ enum nss_status _nss_systemd_setpwent(int stayopen) {
* (think: LDAP/NIS type situations), and our synthesizing of root/nobody is a robustness fallback
* only, which matters for getpwnam()/getpwuid() primarily, which are the main NSS entrypoints to the
* user database. */
r = userdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getpwent_data.iterator);
r = userdb_all(/* match= */ NULL, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getpwent_data.iterator);
return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
}
@ -639,7 +639,7 @@ enum nss_status _nss_systemd_setgrent(int stayopen) {
getgrent_data.by_membership = false;
/* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE_INTRINSIC here */
r = groupdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getgrent_data.iterator);
r = groupdb_all(/* match= */ NULL, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getgrent_data.iterator);
return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
}
@ -659,7 +659,7 @@ enum nss_status _nss_systemd_setspent(int stayopen) {
getspent_data.by_membership = false;
/* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE_INTRINSIC here */
r = userdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getspent_data.iterator);
r = userdb_all(/* match= */ NULL, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getspent_data.iterator);
return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
}
@ -679,7 +679,7 @@ enum nss_status _nss_systemd_setsgent(int stayopen) {
getsgent_data.by_membership = false;
/* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
r = groupdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getsgent_data.iterator);
r = groupdb_all(/* match= */ NULL, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getsgent_data.iterator);
return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
}
@ -709,7 +709,7 @@ enum nss_status _nss_systemd_getpwent_r(
return NSS_STATUS_UNAVAIL;
}
r = userdb_iterator_get(getpwent_data.iterator, &ur);
r = userdb_iterator_get(getpwent_data.iterator, /* match= */ NULL, &ur);
if (r == -ESRCH)
return NSS_STATUS_NOTFOUND;
if (r < 0) {
@ -756,7 +756,7 @@ enum nss_status _nss_systemd_getgrent_r(
}
if (!getgrent_data.by_membership) {
r = groupdb_iterator_get(getgrent_data.iterator, &gr);
r = groupdb_iterator_get(getgrent_data.iterator, /* match= */ NULL, &gr);
if (r == -ESRCH) {
/* So we finished iterating native groups now. Let's now continue with iterating
* native memberships, and generate additional group entries for any groups
@ -882,7 +882,7 @@ enum nss_status _nss_systemd_getspent_r(
}
for (;;) {
r = userdb_iterator_get(getspent_data.iterator, &ur);
r = userdb_iterator_get(getspent_data.iterator, /* match= */ NULL, &ur);
if (r == -ESRCH)
return NSS_STATUS_NOTFOUND;
if (r < 0) {
@ -934,7 +934,7 @@ enum nss_status _nss_systemd_getsgent_r(
}
for (;;) {
r = groupdb_iterator_get(getsgent_data.iterator, &gr);
r = groupdb_iterator_get(getsgent_data.iterator, /* match= */ NULL, &gr);
if (r == -ESRCH)
return NSS_STATUS_NOTFOUND;
if (r < 0) {
@ -1014,7 +1014,7 @@ enum nss_status _nss_systemd_initgroups_dyn(
/* The group might be defined via traditional NSS only, hence let's do a full look-up without
* disabling NSS. This means we are operating recursively here. */
r = groupdb_by_name(group_name, (nss_glue_userdb_flags() & ~USERDB_EXCLUDE_NSS) | USERDB_SUPPRESS_SHADOW, &g);
r = groupdb_by_name(group_name, /* match= */ NULL, (nss_glue_userdb_flags() & ~USERDB_EXCLUDE_NSS) | USERDB_SUPPRESS_SHADOW, &g);
if (r == -ESRCH)
continue;
if (r < 0) {

View File

@ -81,7 +81,7 @@ enum nss_status userdb_getpwnam(
if (_nss_systemd_is_blocked())
return NSS_STATUS_NOTFOUND;
r = userdb_by_name(name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &hr);
r = userdb_by_name(name, /* match= */ NULL, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &hr);
if (r == -ESRCH)
return NSS_STATUS_NOTFOUND;
if (r < 0) {
@ -114,7 +114,7 @@ enum nss_status userdb_getpwuid(
if (_nss_systemd_is_blocked())
return NSS_STATUS_NOTFOUND;
r = userdb_by_uid(uid, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &hr);
r = userdb_by_uid(uid, /* match= */ NULL, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &hr);
if (r == -ESRCH)
return NSS_STATUS_NOTFOUND;
if (r < 0) {
@ -190,7 +190,7 @@ enum nss_status userdb_getspnam(
if (_nss_systemd_is_blocked())
return NSS_STATUS_NOTFOUND;
r = userdb_by_name(name, nss_glue_userdb_flags(), &hr);
r = userdb_by_name(name, /* match= */ NULL, nss_glue_userdb_flags(), &hr);
if (r == -ESRCH)
return NSS_STATUS_NOTFOUND;
if (r < 0) {
@ -290,7 +290,7 @@ enum nss_status userdb_getgrnam(
if (_nss_systemd_is_blocked())
return NSS_STATUS_NOTFOUND;
r = groupdb_by_name(name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &g);
r = groupdb_by_name(name, /* match= */ NULL, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &g);
if (r < 0 && r != -ESRCH) {
*errnop = -r;
return NSS_STATUS_UNAVAIL;
@ -357,7 +357,7 @@ enum nss_status userdb_getgrgid(
if (_nss_systemd_is_blocked())
return NSS_STATUS_NOTFOUND;
r = groupdb_by_gid(gid, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &g);
r = groupdb_by_gid(gid, /* match= */ NULL, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &g);
if (r < 0 && r != -ESRCH) {
*errnop = -r;
return NSS_STATUS_UNAVAIL;
@ -456,7 +456,7 @@ enum nss_status userdb_getsgnam(
if (_nss_systemd_is_blocked())
return NSS_STATUS_NOTFOUND;
r = groupdb_by_name(name, nss_glue_userdb_flags(), &hr);
r = groupdb_by_name(name, /* match= */ NULL, nss_glue_userdb_flags(), &hr);
if (r == -ESRCH)
return NSS_STATUS_NOTFOUND;
if (r < 0) {

View File

@ -221,7 +221,7 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
if (show_full_group_info) {
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
r = groupdb_by_gid(hr->gid, 0, &gr);
r = groupdb_by_gid(hr->gid, /* match= */ NULL, /* flags= */ 0, &gr);
if (r < 0) {
errno = -r;
printf(" GID: " GID_FMT " (unresolvable: %m)\n", hr->gid);

View File

@ -2796,6 +2796,39 @@ int user_record_match(UserRecord *u, const UserDBMatch *match) {
return true;
}
int json_dispatch_dispositions_mask(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
uint64_t *mask = ASSERT_PTR(userdata);
if (sd_json_variant_is_null(variant)) {
*mask = UINT64_MAX;
return 0;
}
if (!sd_json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
uint64_t m = 0;
for (size_t i = 0; i < sd_json_variant_elements(variant); i++) {
sd_json_variant *e;
const char *a;
e = sd_json_variant_by_index(variant, i);
if (!sd_json_variant_is_string(e))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
assert_se(a = sd_json_variant_string(e));
UserDisposition d = user_disposition_from_string(a);
if (d < 0)
return json_log(e, flags, d, "JSON field '%s' contains an invalid user disposition type: %s", strna(name), a);
m |= INDEX_TO_MASK(uint64_t, d);
}
*mask = m;
return 0;
}
static const char* const user_storage_table[_USER_STORAGE_MAX] = {
[USER_CLASSIC] = "classic",
[USER_LUKS] = "luks",

View File

@ -9,7 +9,9 @@
#include "hashmap.h"
#include "missing_resource.h"
#include "strv.h"
#include "time-util.h"
#include "user-util.h"
typedef enum UserDisposition {
USER_INTRINSIC, /* root and nobody */
@ -503,13 +505,37 @@ typedef struct UserDBMatch {
};
} UserDBMatch;
#define USER_DISPOSITION_MASK_MAX ((UINT64_C(1) << _USER_DISPOSITION_MAX) - UINT64_C(1))
#define USER_DISPOSITION_MASK_ALL ((UINT64_C(1) << _USER_DISPOSITION_MAX) - UINT64_C(1))
#define USERDB_MATCH_NULL \
(UserDBMatch) { \
.disposition_mask = USER_DISPOSITION_MASK_ALL, \
.uid_min = 0, \
.uid_max = UID_INVALID-1, \
}
static inline bool userdb_match_is_set(const UserDBMatch *match) {
if (!match)
return false;
return !strv_isempty(match->fuzzy_names) ||
!FLAGS_SET(match->disposition_mask, USER_DISPOSITION_MASK_ALL) ||
match->uid_min > 0 ||
match->uid_max < UID_INVALID-1;
}
static inline void userdb_match_done(UserDBMatch *match) {
assert(match);
strv_free(match->fuzzy_names);
}
bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches);
int user_record_match(UserRecord *u, const UserDBMatch *match);
bool user_record_matches_user_name(const UserRecord *u, const char *username);
int json_dispatch_dispositions_mask(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
const char* user_storage_to_string(UserStorage t) _const_;
UserStorage user_storage_from_string(const char *s) _pure_;

View File

@ -4,6 +4,7 @@
#include "sd-varlink.h"
#include "bitfield.h"
#include "conf-files.h"
#include "dirent-util.h"
#include "dlfcn-util.h"
@ -35,16 +36,23 @@ struct UserDBIterator {
LookupWhat what;
UserDBFlags flags;
Set *links;
const char *method; /* Note, this is a const static string! */
sd_json_variant *query;
bool more:1;
bool nss_covered:1;
bool nss_iterating:1;
bool dropin_covered:1;
bool synthesize_root:1;
bool synthesize_nobody:1;
bool nss_systemd_blocked:1;
char **dropins;
size_t current_dropin;
int error;
unsigned n_found;
sd_event *event;
UserRecord *found_user; /* when .what == LOOKUP_USER */
GroupRecord *found_group; /* when .what == LOOKUP_GROUP */
@ -55,10 +63,14 @@ struct UserDBIterator {
char *filter_user_name, *filter_group_name;
};
static int userdb_connect(UserDBIterator *iterator, const char *path, const char *method, bool more, sd_json_variant *query);
UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
if (!iterator)
return NULL;
sd_json_variant_unref(iterator->query);
set_free(iterator->links);
strv_free(iterator->dropins);
@ -159,6 +171,70 @@ static void membership_data_done(struct membership_data *d) {
free(d->group_name);
}
static int userdb_maybe_restart_query(
UserDBIterator *iterator,
sd_varlink *link,
sd_json_variant *parameters,
const char *error_id) {
int r;
assert(iterator);
assert(link);
assert(error_id);
/* These fields were added in v258 and didn't exist in previous implementations. Hence, we consider
* their support optional: if any service refuses any of these fields, we'll restart the query
* without them, and apply the filtering they are supposed to do client side. */
static const char *const fields[] = {
"fuzzyNames",
"dispositionMask",
"uidMin",
"uidMax",
"gidMin",
"gidMax",
NULL
};
/* Figure out if the reported error indicates any of the suppressable fields are at fault, and that
* our query actually included them */
bool restart = false;
STRV_FOREACH(f, fields) {
if (!sd_varlink_error_is_invalid_parameter(error_id, parameters, *f))
continue;
if (!sd_json_variant_by_key(iterator->query, *f))
continue;
restart = true;
break;
}
if (!restart)
return 0;
/* Now patch the fields out */
_cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query =
sd_json_variant_ref(iterator->query);
r = sd_json_variant_filter(&patched_query, (char**const) fields);
if (r < 0)
return r;
/* NB: we stored the socket path in the varlink connection description when we set things up here! */
r = userdb_connect(
iterator,
ASSERT_PTR(sd_varlink_get_description(link)),
iterator->method,
iterator->more,
patched_query);
if (r < 0)
return r;
log_debug("Restarted query to service '%s' due to missing features.", sd_varlink_get_description(link));
return 1;
}
static int userdb_on_query_reply(
sd_varlink *link,
sd_json_variant *parameters,
@ -172,6 +248,14 @@ static int userdb_on_query_reply(
if (error_id) {
log_debug("Got lookup error: %s", error_id);
r = userdb_maybe_restart_query(iterator, link, parameters, error_id);
if (r < 0)
return r;
if (r > 0) {
r = 0;
goto finish;
}
/* Convert various forms of record not found into -ESRCH, since NSS typically doesn't care,
* about the details. Note that if a userName specification is refused as invalid parameter,
* we also turn this into -ESRCH following the logic that there cannot be a user record for a
@ -182,6 +266,8 @@ static int userdb_on_query_reply(
sd_varlink_error_is_invalid_parameter(error_id, parameters, "userName") ||
sd_varlink_error_is_invalid_parameter(error_id, parameters, "groupName"))
r = -ESRCH;
else if (streq(error_id, "io.systemd.UserDatabase.NonMatchingRecordFound"))
r = -ENOEXEC;
else if (streq(error_id, "io.systemd.UserDatabase.ServiceNotAvailable"))
r = -EHOSTDOWN;
else if (streq(error_id, "io.systemd.UserDatabase.EnumerationNotSupported"))
@ -338,9 +424,9 @@ static int userdb_on_query_reply(
}
finish:
/* If we got one ESRCH, let that win. This way when we do a wild dump we won't be tripped up by bad
* errors if at least one connection ended cleanly */
if (r == -ESRCH || iterator->error == 0)
/* If we got one ESRCH or ENOEXEC, let that win. This way when we do a wild dump we won't be tripped
* up by bad errors as long as at least one connection ended somewhat cleanly */
if (IN_SET(r, -ESRCH, -ENOEXEC) || iterator->error == 0)
iterator->error = -r;
assert_se(set_remove(iterator->links, link) == link);
@ -378,7 +464,12 @@ static int userdb_connect(
if (r < 0)
return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m");
(void) sd_varlink_set_description(vl, path);
/* Note, this is load bearing: we store the socket path as description for the varlink
* connection. That's not just good for debugging, but we reuse this information in case we need to
* reissue the query with a reduced set of parameters. */
r = sd_varlink_set_description(vl, path);
if (r < 0)
return log_debug_errno(r, "Failed to set varlink connection description: %m");
r = sd_varlink_bind_reply(vl, userdb_on_query_reply);
if (r < 0)
@ -410,7 +501,7 @@ static int userdb_connect(
static int userdb_start_query(
UserDBIterator *iterator,
const char *method,
const char *method, /* must be a static string, we are not going to copy this here! */
bool more,
sd_json_variant *query,
UserDBFlags flags) {
@ -426,6 +517,11 @@ static int userdb_start_query(
if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
return -ENOLINK;
assert(!iterator->query);
iterator->method = method; /* note: we don't make a copy here! */
iterator->query = sd_json_variant_ref(query);
iterator->more = more;
e = getenv("SYSTEMD_BYPASS_USERDB");
if (e) {
r = parse_boolean(e);
@ -674,38 +770,61 @@ nomatch:
return 0;
}
int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
static int query_append_disposition_mask(sd_json_variant **query, uint64_t mask) {
int r;
if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
uid_t uid;
assert(query);
if (parse_uid(name, &uid) >= 0)
return userdb_by_uid(uid, flags, ret);
}
if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL))
return 0;
if (!valid_user_group_name(name, VALID_USER_RELAX))
return -EINVAL;
_cleanup_strv_free_ char **dispositions = NULL;
for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
if (!BITS_SET(mask, d))
continue;
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(name)));
if (r < 0)
return r;
iterator = userdb_iterator_new(LOOKUP_USER, flags);
if (!iterator)
return -ENOMEM;
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", false, query, flags);
if (r >= 0) {
r = userdb_process(iterator, ret, NULL, NULL, NULL);
if (r >= 0)
r = strv_extend(&dispositions, user_disposition_to_string(d));
if (r < 0)
return r;
}
return sd_json_variant_merge_objectbo(
query,
SD_JSON_BUILD_PAIR_STRV("dispositionMask", dispositions));
}
static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *match) {
int r;
assert(query);
if (!userdb_match_is_set(match))
return 0;
r = sd_json_variant_merge_objectbo(
query,
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
SD_JSON_BUILD_PAIR_CONDITION(match->uid_min > 0, "uidMin", SD_JSON_BUILD_UNSIGNED(match->uid_min)),
SD_JSON_BUILD_PAIR_CONDITION(match->uid_max < UID_INVALID-1, "uidMax", SD_JSON_BUILD_UNSIGNED(match->uid_max)));
if (r < 0)
return r;
return query_append_disposition_mask(query, match->disposition_mask);
}
static int userdb_by_name_fallbacks(
const char *name,
UserDBIterator *iterator,
UserDBFlags flags,
UserRecord **ret) {
int r;
assert(name);
assert(iterator);
assert(ret);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
r = dropin_user_record_by_name(name, NULL, flags, ret);
r = dropin_user_record_by_name(name, /* path= */ NULL, flags, ret);
if (r >= 0)
return r;
}
@ -737,21 +856,41 @@ int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
return r;
if (r > 0)
return synthetic_foreign_user_build(foreign_uid, ret);
r = -ESRCH;
}
return r;
return -ESRCH;
}
int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
int userdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
int r;
if (!uid_is_valid(uid))
/* Well known errors this returns:
* -EINVAL user name is not valid
* -ESRCH no such user
* -ENOEXEC found a user by request UID or name, but it does not match filter
* -EHOSTDOWN service failed for some reason
* -ETIMEDOUT service timed out
*/
assert(name);
if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
uid_t uid;
if (parse_uid(name, &uid) >= 0)
return userdb_by_uid(uid, match, flags, ret);
}
if (!valid_user_group_name(name, VALID_USER_RELAX))
return -EINVAL;
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(uid)));
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(name)));
if (r < 0)
return r;
r = query_append_uid_match(&query, match);
if (r < 0)
return r;
@ -759,12 +898,45 @@ int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
if (!iterator)
return -ENOMEM;
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", false, query, flags);
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ false, query, flags);
if (r >= 0) {
r = userdb_process(iterator, ret, NULL, NULL, NULL);
if (r >= 0)
r = userdb_process(iterator, &ur, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
if (r == -ENOEXEC) /* found a user matching UID or name, but not filter. In this case the
* fallback paths below are pointless */
return r;
}
if (r < 0) { /* If the above fails for any other reason, try fallback paths */
r = userdb_by_name_fallbacks(name, iterator, flags, &ur);
if (r < 0)
return r;
}
/* NB: we always apply our own filtering here, explicitly, regardless if the server supported it or
* not. It's more robust this way, we never know how carefully the server is written, and whether it
* properly implements all details of the filtering logic. */
r = user_record_match(ur, match);
if (r < 0)
return r;
if (r == 0)
return -ENOEXEC;
if (ret)
*ret = TAKE_PTR(ur);
return 0;
}
static int userdb_by_uid_fallbacks(
uid_t uid,
UserDBIterator *iterator,
UserDBFlags flags,
UserRecord **ret) {
int r;
assert(uid_is_valid(uid));
assert(iterator);
assert(ret);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
r = dropin_user_record_by_uid(uid, NULL, flags, ret);
@ -793,20 +965,70 @@ int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && uid_is_foreign(uid))
return synthetic_foreign_user_build(uid - FOREIGN_UID_BASE, ret);
return r;
return -ESRCH;
}
int userdb_all(UserDBFlags flags, UserDBIterator **ret) {
int userdb_by_uid(uid_t uid, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
int r, qr;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
int r;
assert(ret);
if (!uid_is_valid(uid))
return -EINVAL;
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(uid)));
if (r < 0)
return r;
r = query_append_uid_match(&query, match);
if (r < 0)
return r;
iterator = userdb_iterator_new(LOOKUP_USER, flags);
if (!iterator)
return -ENOMEM;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", true, NULL, flags);
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ false, query, flags);
if (r >= 0) {
r = userdb_process(iterator, &ur, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
if (r == -ENOEXEC)
return r;
}
if (r < 0) {
r = userdb_by_uid_fallbacks(uid, iterator, flags, &ur);
if (r < 0)
return r;
}
r = user_record_match(ur, match);
if (r < 0)
return r;
if (r == 0)
return -ENOEXEC;
if (ret)
*ret = TAKE_PTR(ur);
return 0;
}
int userdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
int r, qr;
assert(ret);
r = query_append_uid_match(&query, match);
if (r < 0)
return r;
iterator = userdb_iterator_new(LOOKUP_USER, flags);
if (!iterator)
return -ENOMEM;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ true, query, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
@ -840,7 +1062,7 @@ int userdb_all(UserDBFlags flags, UserDBIterator **ret) {
return 0;
}
int userdb_iterator_get(UserDBIterator *iterator, UserRecord **ret) {
static int userdb_iterator_get_one(UserDBIterator *iterator, UserRecord **ret) {
int r;
assert(iterator);
@ -928,7 +1150,7 @@ int userdb_iterator_get(UserDBIterator *iterator, UserRecord **ret) {
}
/* Then, let's return the users provided by varlink IPC */
r = userdb_process(iterator, ret, NULL, NULL, NULL);
r = userdb_process(iterator, ret, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
if (r < 0) {
/* Finally, synthesize root + nobody if not done yet */
@ -952,6 +1174,29 @@ int userdb_iterator_get(UserDBIterator *iterator, UserRecord **ret) {
return r;
}
int userdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, UserRecord **ret) {
int r;
assert(iterator);
assert(iterator->what == LOOKUP_USER);
for (;;) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
r = userdb_iterator_get_one(iterator, userdb_match_is_set(match) || ret ? &ur : NULL);
if (r < 0)
return r;
if (ur && !user_record_match(ur, match))
continue;
if (ret)
*ret = TAKE_PTR(ur);
return r;
}
}
static int synthetic_root_group_build(GroupRecord **ret) {
return group_record_build(
ret,
@ -993,43 +1238,44 @@ static int synthetic_foreign_group_build(gid_t foreign_gid, GroupRecord **ret) {
SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign"))));
}
int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
static int query_append_gid_match(sd_json_variant **query, const UserDBMatch *match) {
int r;
if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
gid_t gid;
assert(query);
if (parse_gid(name, &gid) >= 0)
return groupdb_by_gid(gid, flags, ret);
}
if (!userdb_match_is_set(match))
return 0;
if (!valid_user_group_name(name, VALID_USER_RELAX))
return -EINVAL;
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(name)));
r = sd_json_variant_merge_objectbo(
query,
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
SD_JSON_BUILD_PAIR_CONDITION(match->gid_min > 0, "gidMin", SD_JSON_BUILD_UNSIGNED(match->gid_min)),
SD_JSON_BUILD_PAIR_CONDITION(match->gid_max < GID_INVALID-1, "gidMax", SD_JSON_BUILD_UNSIGNED(match->gid_max)));
if (r < 0)
return r;
iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
if (!iterator)
return -ENOMEM;
return query_append_disposition_mask(query, match->disposition_mask);
}
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", false, query, flags);
if (r >= 0) {
r = userdb_process(iterator, NULL, ret, NULL, NULL);
if (r >= 0)
return r;
}
static int groupdb_by_name_fallbacks(
const char *name,
UserDBIterator *iterator,
UserDBFlags flags,
GroupRecord **ret) {
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
int r;
assert(name);
assert(iterator);
assert(ret);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
r = dropin_group_record_by_name(name, NULL, flags, ret);
if (r >= 0)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r >= 0) {
r = nss_group_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
@ -1053,21 +1299,33 @@ int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret) {
return r;
if (r > 0)
return synthetic_foreign_group_build(foreign_gid, ret);
r = -ESRCH;
}
return r;
return -ESRCH;
}
int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret) {
int groupdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
int r;
if (!gid_is_valid(gid))
assert(name);
if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
gid_t gid;
if (parse_gid(name, &gid) >= 0)
return groupdb_by_gid(gid, match, flags, ret);
}
if (!valid_user_group_name(name, VALID_USER_RELAX))
return -EINVAL;
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(gid)));
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(name)));
if (r < 0)
return r;
r = query_append_gid_match(&query, match);
if (r < 0)
return r;
@ -1075,12 +1333,42 @@ int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret) {
if (!iterator)
return -ENOMEM;
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", false, query, flags);
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
if (r >= 0) {
r = userdb_process(iterator, NULL, ret, NULL, NULL);
if (r >= 0)
r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
if (r == -ENOEXEC)
return r;
}
if (r < 0) {
r = groupdb_by_name_fallbacks(name, iterator, flags, &gr);
if (r < 0)
return r;
}
/* As above, we apply our own client-side filtering even if server-side filtering worked, for robustness and simplicity reasons. */
r = group_record_match(gr, match);
if (r < 0)
return r;
if (r == 0)
return -ENOEXEC;
if (ret)
*ret = TAKE_PTR(gr);
return r;
}
static int groupdb_by_gid_fallbacks(
gid_t gid,
UserDBIterator *iterator,
UserDBFlags flags,
GroupRecord **ret) {
int r;
assert(gid_is_valid(gid));
assert(iterator);
assert(ret);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
r = dropin_group_record_by_gid(gid, NULL, flags, ret);
@ -1108,20 +1396,70 @@ int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret) {
if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && gid_is_foreign(gid))
return synthetic_foreign_group_build(gid - FOREIGN_UID_BASE, ret);
return r;
return -ESRCH;
}
int groupdb_all(UserDBFlags flags, UserDBIterator **ret) {
int groupdb_by_gid(gid_t gid, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
int r, qr;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
int r;
assert(ret);
if (!gid_is_valid(gid))
return -EINVAL;
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(gid)));
if (r < 0)
return r;
r = query_append_gid_match(&query, match);
if (r < 0)
return r;
iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
if (!iterator)
return -ENOMEM;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", true, NULL, flags);
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
if (r >= 0) {
r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
if (r == -ENOEXEC)
return r;
}
if (r < 0) {
r = groupdb_by_gid_fallbacks(gid, iterator, flags, &gr);
if (r < 0)
return r;
}
r = group_record_match(gr, match);
if (r < 0)
return r;
if (r == 0)
return -ENOEXEC;
if (ret)
*ret = TAKE_PTR(gr);
return 0;
}
int groupdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
int r, qr;
assert(ret);
r = query_append_gid_match(&query, match);
if (r < 0)
return r;
iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
if (!iterator)
return -ENOMEM;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ true, query, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
@ -1152,7 +1490,7 @@ int groupdb_all(UserDBFlags flags, UserDBIterator **ret) {
return 0;
}
int groupdb_iterator_get(UserDBIterator *iterator, GroupRecord **ret) {
static int groupdb_iterator_get_one(UserDBIterator *iterator, GroupRecord **ret) {
int r;
assert(iterator);
@ -1254,6 +1592,29 @@ int groupdb_iterator_get(UserDBIterator *iterator, GroupRecord **ret) {
return r;
}
int groupdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, GroupRecord **ret) {
int r;
assert(iterator);
assert(iterator->what == LOOKUP_GROUP);
for (;;) {
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
r = groupdb_iterator_get_one(iterator, userdb_match_is_set(match) || ret ? &gr : NULL);
if (r < 0)
return r;
if (gr && !group_record_match(gr, match))
continue;
if (ret)
*ret = TAKE_PTR(gr);
return r;
}
}
static void discover_membership_dropins(UserDBIterator *i, UserDBFlags flags) {
int r;

View File

@ -42,15 +42,15 @@ typedef enum UserDBFlags {
* -ETIMEDOUT: Time-out
*/
int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret);
int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret);
int userdb_all(UserDBFlags flags, UserDBIterator **ret);
int userdb_iterator_get(UserDBIterator *iterator, UserRecord **ret);
int userdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret);
int userdb_by_uid(uid_t uid, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret);
int userdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret);
int userdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, UserRecord **ret);
int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret);
int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret);
int groupdb_all(UserDBFlags flags, UserDBIterator **ret);
int groupdb_iterator_get(UserDBIterator *iterator, GroupRecord **ret);
int groupdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret);
int groupdb_by_gid(gid_t gid, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret);
int groupdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret);
int groupdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, GroupRecord **ret);
int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret);
int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret);

View File

@ -10,6 +10,15 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
SD_VARLINK_FIELD_COMMENT("The UNIX user name of the record, if look-up by name is desired."),
SD_VARLINK_DEFINE_INPUT(userName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The userdb provider service to search on. Must be set to the base name of the userdb entrypoint socket. This is necessary in order to support services that implement multiple userdb services on the same socket."),
SD_VARLINK_FIELD_COMMENT("Names to search for in a fuzzy fashion."),
SD_VARLINK_DEFINE_INPUT(fuzzyNames, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("User dispositions to limit search by."),
SD_VARLINK_DEFINE_INPUT(dispositionMask, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Minimum UID to restrict search too."),
SD_VARLINK_DEFINE_INPUT(uidMin, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Maximum UID to restrict search too."),
SD_VARLINK_DEFINE_INPUT(uidMax, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The userdb provider to search on. Must be set to the name of the userdb entrypoint socket. This is necessary in order to support services that implement multiple userdb services on the same socket."),
SD_VARLINK_DEFINE_INPUT(service, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("The retrieved user record."),
SD_VARLINK_DEFINE_OUTPUT(record, SD_VARLINK_OBJECT, 0),
@ -24,6 +33,15 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
SD_VARLINK_FIELD_COMMENT("The UNIX group name of the record, if look-up by name is desired."),
SD_VARLINK_DEFINE_INPUT(groupName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The userdb provider service to search on. Must be set to the base name of the userdb entrypoint socket. This is necessary in order to support services that implement multiple userdb services on the same socket."),
SD_VARLINK_FIELD_COMMENT("Additional names to search for in a fuzzy fashion."),
SD_VARLINK_DEFINE_INPUT(fuzzyNames, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Group dispositions to limit search by."),
SD_VARLINK_DEFINE_INPUT(dispositionMask, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Minimum GID to restrict search too."),
SD_VARLINK_DEFINE_INPUT(gidMin, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Maximum GID to restrict search too."),
SD_VARLINK_DEFINE_INPUT(gidMax, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The userdb provider to search on. Must be set to the name of the userdb entrypoint socket. This is necessary in order to support services that implement multiple userdb services on the same socket."),
SD_VARLINK_DEFINE_INPUT(service, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("The retrieved group record."),
SD_VARLINK_DEFINE_OUTPUT(record, SD_VARLINK_OBJECT, 0),
@ -49,6 +67,7 @@ static SD_VARLINK_DEFINE_ERROR(BadService);
static SD_VARLINK_DEFINE_ERROR(ServiceNotAvailable);
static SD_VARLINK_DEFINE_ERROR(ConflictingRecordFound);
static SD_VARLINK_DEFINE_ERROR(EnumerationNotSupported);
static SD_VARLINK_DEFINE_ERROR(NonMatchingRecordFound);
/* As per https://systemd.io/USER_GROUP_API/ */
SD_VARLINK_DEFINE_INTERFACE(
@ -69,5 +88,7 @@ SD_VARLINK_DEFINE_INTERFACE(
&vl_error_ServiceNotAvailable,
SD_VARLINK_SYMBOL_COMMENT("Error indicating that there's a user record matching either UID/GID or the user/group name, but not both at the same time."),
&vl_error_ConflictingRecordFound,
SD_VARLINK_SYMBOL_COMMENT("Error indicating that there's a user record matching the primary UID/GID or user/group, but that doesn't match the additional specified matches."),
&vl_error_NonMatchingRecordFound,
SD_VARLINK_SYMBOL_COMMENT("Error indicating that retrieval of user/group records on this service is only supported if either user/group name or UID/GID are specified, but not if nothing is specified."),
&vl_error_EnumerationNotSupported);

View File

@ -396,7 +396,7 @@ static int display_user(int argc, char *argv[], void *userdata) {
(void) table_hide_column_from_display(table, (size_t) 0);
}
UserDBMatch match = {
_cleanup_(userdb_match_done) UserDBMatch match = {
.disposition_mask = arg_disposition_mask,
.uid_min = arg_uid_min,
.uid_max = arg_uid_max,
@ -406,19 +406,18 @@ static int display_user(int argc, char *argv[], void *userdata) {
STRV_FOREACH(i, argv + 1) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
r = userdb_by_name(*i, arg_userdb_flags|USERDB_PARSE_NUMERIC, &ur);
r = userdb_by_name(*i, &match, arg_userdb_flags|USERDB_PARSE_NUMERIC, &ur);
if (r < 0) {
if (r == -ESRCH)
log_error_errno(r, "User %s does not exist.", *i);
else if (r == -EHOSTDOWN)
log_error_errno(r, "Selected user database service is not available for this request.");
else if (r == -ENOEXEC)
log_error_errno(r, "User '%s' exists but does not match specified filter.", *i);
else
log_error_errno(r, "Failed to find user %s: %m", *i);
RET_GATHER(ret, r);
} else if (!user_record_match(ur, &match)) {
log_error("User '%s' does not match filter.", *i);
RET_GATHER(ret, -ENOEXEC);
} else {
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
@ -431,18 +430,15 @@ static int display_user(int argc, char *argv[], void *userdata) {
}
}
else {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_strv_free_ char **names = NULL;
if (argc > 1) {
names = strv_copy(argv + 1);
if (!names)
/* If there are further arguments, they are the fuzzy match strings. */
match.fuzzy_names = strv_copy(strv_skip(argv, 1));
if (!match.fuzzy_names)
return log_oom();
match.fuzzy_names = names;
}
r = userdb_all(arg_userdb_flags, &iterator);
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
r = userdb_all(&match, arg_userdb_flags, &iterator);
if (r == -ENOLINK) /* ENOLINK → Didn't find answer without Varlink, and didn't try Varlink because was configured to off. */
log_debug_errno(r, "No entries found. (Didn't check via Varlink.)");
else if (r == -ESRCH) /* ESRCH → Couldn't find any suitable entry, but we checked all sources */
@ -453,7 +449,7 @@ static int display_user(int argc, char *argv[], void *userdata) {
for (;;) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
r = userdb_iterator_get(iterator, &ur);
r = userdb_iterator_get(iterator, &match, &ur);
if (r == -ESRCH)
break;
if (r == -EHOSTDOWN)
@ -461,9 +457,6 @@ static int display_user(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed acquire next user: %m");
if (!user_record_match(ur, &match))
continue;
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
@ -728,7 +721,7 @@ static int display_group(int argc, char *argv[], void *userdata) {
(void) table_hide_column_from_display(table, (size_t) 0);
}
UserDBMatch match = {
_cleanup_(userdb_match_done) UserDBMatch match = {
.disposition_mask = arg_disposition_mask,
.gid_min = arg_uid_min,
.gid_max = arg_uid_max,
@ -738,19 +731,18 @@ static int display_group(int argc, char *argv[], void *userdata) {
STRV_FOREACH(i, argv + 1) {
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
r = groupdb_by_name(*i, arg_userdb_flags|USERDB_PARSE_NUMERIC, &gr);
r = groupdb_by_name(*i, &match, arg_userdb_flags|USERDB_PARSE_NUMERIC, &gr);
if (r < 0) {
if (r == -ESRCH)
log_error_errno(r, "Group %s does not exist.", *i);
else if (r == -EHOSTDOWN)
log_error_errno(r, "Selected group database service is not available for this request.");
else if (r == -ENOEXEC)
log_error_errno(r, "Group '%s' exists but does not match specified filter.", *i);
else
log_error_errno(r, "Failed to find group %s: %m", *i);
RET_GATHER(ret, r);
} else if (!group_record_match(gr, &match)) {
log_error("Group '%s' does not match filter.", *i);
RET_GATHER(ret, -ENOEXEC);
} else {
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
@ -763,18 +755,14 @@ static int display_group(int argc, char *argv[], void *userdata) {
}
}
else {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_strv_free_ char **names = NULL;
if (argc > 1) {
names = strv_copy(argv + 1);
if (!names)
match.fuzzy_names = strv_copy(strv_skip(argv, 1));
if (!match.fuzzy_names)
return log_oom();
match.fuzzy_names = names;
}
r = groupdb_all(arg_userdb_flags, &iterator);
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
r = groupdb_all(&match, arg_userdb_flags, &iterator);
if (r == -ENOLINK)
log_debug_errno(r, "No entries found. (Didn't check via Varlink.)");
else if (r == -ESRCH)
@ -785,7 +773,7 @@ static int display_group(int argc, char *argv[], void *userdata) {
for (;;) {
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
r = groupdb_iterator_get(iterator, &gr);
r = groupdb_iterator_get(iterator, &match, &gr);
if (r == -ESRCH)
break;
if (r == -EHOSTDOWN)
@ -793,9 +781,6 @@ static int display_group(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed acquire next group: %m");
if (!group_record_match(gr, &match))
continue;
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
@ -1089,7 +1074,7 @@ static int ssh_authorized_keys(int argc, char *argv[], void *userdata) {
chain_invocation = NULL;
}
r = userdb_by_name(argv[1], arg_userdb_flags, &ur);
r = userdb_by_name(argv[1], /* match= */ NULL, arg_userdb_flags, &ur);
if (r == -ESRCH)
log_error_errno(r, "User %s does not exist.", argv[1]);
else if (r == -EHOSTDOWN)
@ -1448,7 +1433,7 @@ static int parse_argv(int argc, char *argv[]) {
/* If not mask was specified, use the all bits on mask */
if (arg_disposition_mask == UINT64_MAX)
arg_disposition_mask = USER_DISPOSITION_MASK_MAX;
arg_disposition_mask = USER_DISPOSITION_MASK_ALL;
return 1;
}

View File

@ -35,8 +35,15 @@ typedef struct LookupParameters {
gid_t gid;
};
const char *service;
UserDBMatch match;
} LookupParameters;
static void lookup_parameters_done(LookupParameters *p) {
assert(p);
userdb_match_done(&p->match);
}
static int add_nss_service(sd_json_variant **v) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *status = NULL, *z = NULL;
sd_id128_t mid;
@ -134,16 +141,21 @@ static int userdb_flags_from_service(sd_varlink *link, const char *service, User
static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "uid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 },
{ "userName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, name), SD_JSON_RELAX },
{ "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
{ "uid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 },
{ "userName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, name), SD_JSON_RELAX },
{ "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
{ "fuzzyNames", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(LookupParameters, match.fuzzy_names), 0 },
{ "dispositionMask", SD_JSON_VARIANT_ARRAY, json_dispatch_dispositions_mask, offsetof(LookupParameters, match.disposition_mask), 0 },
{ "uidMin", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.uid_min), 0 },
{ "uidMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.uid_max), 0 },
{}
};
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
LookupParameters p = {
_cleanup_(lookup_parameters_done) LookupParameters p = {
.uid = UID_INVALID,
.match = USERDB_MATCH_NULL,
};
UserDBFlags userdb_flags;
int r;
@ -160,14 +172,14 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete
return r;
if (uid_is_valid(p.uid))
r = userdb_by_uid(p.uid, userdb_flags, &hr);
r = userdb_by_uid(p.uid, &p.match, userdb_flags, &hr);
else if (p.name)
r = userdb_by_name(p.name, userdb_flags, &hr);
r = userdb_by_name(p.name, &p.match, userdb_flags, &hr);
else {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *last = NULL;
r = userdb_all(userdb_flags, &iterator);
r = userdb_all(&p.match, userdb_flags, &iterator);
if (IN_SET(r, -ESRCH, -ENOLINK))
/* We turn off Varlink lookups in various cases (e.g. in case we only enable DropIn
* backend) this might make userdb_all return ENOLINK (which indicates that varlink
@ -182,7 +194,7 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete
for (;;) {
_cleanup_(user_record_unrefp) UserRecord *z = NULL;
r = userdb_iterator_get(iterator, &z);
r = userdb_iterator_get(iterator, &p.match, &z);
if (r == -ESRCH)
break;
if (r < 0)
@ -208,6 +220,8 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete
}
if (r == -ESRCH)
return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
if (r == -ENOEXEC)
return sd_varlink_error(link, "io.systemd.UserDatabase.NonMatchingRecordFound", NULL);
if (r < 0) {
log_debug_errno(r, "User lookup failed abnormally: %m");
return sd_varlink_error(link, "io.systemd.UserDatabase.ServiceNotAvailable", NULL);
@ -271,16 +285,21 @@ static int build_group_json(sd_varlink *link, GroupRecord *gr, sd_json_variant *
static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "gid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 },
{ "groupName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, name), SD_JSON_RELAX },
{ "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
{ "gid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 },
{ "groupName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, name), SD_JSON_RELAX },
{ "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
{ "fuzzyNames", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(LookupParameters, match.fuzzy_names), 0 },
{ "dispositionMask", SD_JSON_VARIANT_ARRAY, json_dispatch_dispositions_mask, offsetof(LookupParameters, match.disposition_mask), 0 },
{ "gidMin", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.gid_min), 0 },
{ "gidMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.gid_max), 0 },
{}
};
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
LookupParameters p = {
_cleanup_(lookup_parameters_done) LookupParameters p = {
.gid = GID_INVALID,
.match = USERDB_MATCH_NULL,
};
UserDBFlags userdb_flags;
int r;
@ -296,14 +315,14 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet
return r;
if (gid_is_valid(p.gid))
r = groupdb_by_gid(p.gid, userdb_flags, &g);
r = groupdb_by_gid(p.gid, &p.match, userdb_flags, &g);
else if (p.name)
r = groupdb_by_name(p.name, userdb_flags, &g);
r = groupdb_by_name(p.name, &p.match, userdb_flags, &g);
else {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *last = NULL;
r = groupdb_all(userdb_flags, &iterator);
r = groupdb_all(&p.match, userdb_flags, &iterator);
if (IN_SET(r, -ESRCH, -ENOLINK))
return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
if (r < 0)
@ -312,7 +331,7 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet
for (;;) {
_cleanup_(group_record_unrefp) GroupRecord *z = NULL;
r = groupdb_iterator_get(iterator, &z);
r = groupdb_iterator_get(iterator, &p.match, &z);
if (r == -ESRCH)
break;
if (r < 0)
@ -338,6 +357,8 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet
}
if (r == -ESRCH)
return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
if (r == -ENOEXEC)
return sd_varlink_error(link, "io.systemd.UserDatabase.NonMatchingRecordFound", NULL);
if (r < 0) {
log_debug_errno(r, "Group lookup failed abnormally: %m");
return sd_varlink_error(link, "io.systemd.UserDatabase.ServiceNotAvailable", NULL);