Files
systemd/src/shared/userdb.c
Zbigniew Jędrzejewski-Szmek 04499a70fb Drop the text argument from assert_not_reached()
In general we almost never hit those asserts in production code, so users see
them very rarely, if ever. But either way, we just need something that users
can pass to the developers.

We have quite a few of those asserts, and some have fairly nice messages, but
many are like "WTF?" or "???" or "unexpected something". The error that is
printed includes the file location, and function name. In almost all functions
there's at most one assert, so the function name alone is enough to identify
the failure for a developer. So we don't get much extra from the message, and
we might just as well drop them.

Dropping them makes our code a tiny bit smaller, and most importantly, improves
development experience by making it easy to insert such an assert in the code
without thinking how to phrase the argument.
2021-08-03 10:05:10 +02:00

1475 lines
53 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/auxv.h>
#include "conf-files.h"
#include "dirent-util.h"
#include "dlfcn-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "missing_syscall.h"
#include "parse-util.h"
#include "set.h"
#include "socket-util.h"
#include "strv.h"
#include "user-record-nss.h"
#include "user-util.h"
#include "userdb-dropin.h"
#include "userdb.h"
#include "varlink.h"
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(link_hash_ops, void, trivial_hash_func, trivial_compare_func, Varlink, varlink_unref);
typedef enum LookupWhat {
LOOKUP_USER,
LOOKUP_GROUP,
LOOKUP_MEMBERSHIP,
_LOOKUP_WHAT_MAX,
} LookupWhat;
struct UserDBIterator {
LookupWhat what;
UserDBFlags flags;
Set *links;
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 */
char *found_user_name, *found_group_name; /* when .what == LOOKUP_MEMBERSHIP */
char **members_of_group;
size_t index_members_of_group;
char *filter_user_name, *filter_group_name;
};
UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
if (!iterator)
return NULL;
set_free(iterator->links);
strv_free(iterator->dropins);
switch (iterator->what) {
case LOOKUP_USER:
user_record_unref(iterator->found_user);
if (iterator->nss_iterating)
endpwent();
break;
case LOOKUP_GROUP:
group_record_unref(iterator->found_group);
if (iterator->nss_iterating)
endgrent();
break;
case LOOKUP_MEMBERSHIP:
free(iterator->found_user_name);
free(iterator->found_group_name);
strv_free(iterator->members_of_group);
free(iterator->filter_user_name);
free(iterator->filter_group_name);
if (iterator->nss_iterating)
endgrent();
break;
default:
assert_not_reached();
}
sd_event_unref(iterator->event);
if (iterator->nss_systemd_blocked)
assert_se(userdb_block_nss_systemd(false) >= 0);
return mfree(iterator);
}
static UserDBIterator* userdb_iterator_new(LookupWhat what, UserDBFlags flags) {
UserDBIterator *i;
assert(what >= 0);
assert(what < _LOOKUP_WHAT_MAX);
i = new(UserDBIterator, 1);
if (!i)
return NULL;
*i = (UserDBIterator) {
.what = what,
.flags = flags,
.synthesize_root = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE),
.synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE),
};
return i;
}
static int userdb_iterator_block_nss_systemd(UserDBIterator *iterator) {
int r;
assert(iterator);
if (iterator->nss_systemd_blocked)
return 0;
r = userdb_block_nss_systemd(true);
if (r < 0)
return r;
iterator->nss_systemd_blocked = true;
return 1;
}
struct user_group_data {
JsonVariant *record;
bool incomplete;
};
static void user_group_data_release(struct user_group_data *d) {
json_variant_unref(d->record);
}
static int userdb_on_query_reply(
Varlink *link,
JsonVariant *parameters,
const char *error_id,
VarlinkReplyFlags flags,
void *userdata) {
UserDBIterator *iterator = userdata;
int r;
assert(iterator);
if (error_id) {
log_debug("Got lookup error: %s", error_id);
if (STR_IN_SET(error_id,
"io.systemd.UserDatabase.NoRecordFound",
"io.systemd.UserDatabase.ConflictingRecordFound"))
r = -ESRCH;
else if (streq(error_id, "io.systemd.UserDatabase.ServiceNotAvailable"))
r = -EHOSTDOWN;
else if (streq(error_id, "io.systemd.UserDatabase.EnumerationNotSupported"))
r = -EOPNOTSUPP;
else if (streq(error_id, VARLINK_ERROR_TIMEOUT))
r = -ETIMEDOUT;
else
r = -EIO;
goto finish;
}
switch (iterator->what) {
case LOOKUP_USER: {
_cleanup_(user_group_data_release) struct user_group_data user_data = {};
static const JsonDispatch dispatch_table[] = {
{ "record", _JSON_VARIANT_TYPE_INVALID, json_dispatch_variant, offsetof(struct user_group_data, record), 0 },
{ "incomplete", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct user_group_data, incomplete), 0 },
{}
};
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
assert_se(!iterator->found_user);
r = json_dispatch(parameters, dispatch_table, NULL, 0, &user_data);
if (r < 0)
goto finish;
if (!user_data.record) {
r = log_debug_errno(SYNTHETIC_ERRNO(EIO), "Reply is missing record key");
goto finish;
}
hr = user_record_new();
if (!hr) {
r = -ENOMEM;
goto finish;
}
r = user_record_load(hr, user_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
if (r < 0)
goto finish;
if (!hr->service) {
r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "User record does not carry service information, refusing.");
goto finish;
}
hr->incomplete = user_data.incomplete;
/* We match the root user by the name since the name is our primary key. We match the nobody
* use by UID though, since the name might differ on OSes */
if (streq_ptr(hr->user_name, "root"))
iterator->synthesize_root = false;
if (hr->uid == UID_NOBODY)
iterator->synthesize_nobody = false;
iterator->found_user = TAKE_PTR(hr);
iterator->n_found++;
/* More stuff coming? then let's just exit cleanly here */
if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
return 0;
/* Otherwise, let's remove this link and exit cleanly then */
r = 0;
goto finish;
}
case LOOKUP_GROUP: {
_cleanup_(user_group_data_release) struct user_group_data group_data = {};
static const JsonDispatch dispatch_table[] = {
{ "record", _JSON_VARIANT_TYPE_INVALID, json_dispatch_variant, offsetof(struct user_group_data, record), 0 },
{ "incomplete", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct user_group_data, incomplete), 0 },
{}
};
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
assert_se(!iterator->found_group);
r = json_dispatch(parameters, dispatch_table, NULL, 0, &group_data);
if (r < 0)
goto finish;
if (!group_data.record) {
r = log_debug_errno(SYNTHETIC_ERRNO(EIO), "Reply is missing record key");
goto finish;
}
g = group_record_new();
if (!g) {
r = -ENOMEM;
goto finish;
}
r = group_record_load(g, group_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
if (r < 0)
goto finish;
if (!g->service) {
r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Group record does not carry service information, refusing.");
goto finish;
}
g->incomplete = group_data.incomplete;
if (streq_ptr(g->group_name, "root"))
iterator->synthesize_root = false;
if (g->gid == GID_NOBODY)
iterator->synthesize_nobody = false;
iterator->found_group = TAKE_PTR(g);
iterator->n_found++;
if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
return 0;
r = 0;
goto finish;
}
case LOOKUP_MEMBERSHIP: {
struct membership_data {
const char *user_name;
const char *group_name;
} membership_data = {};
static const JsonDispatch dispatch_table[] = {
{ "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(struct membership_data, user_name), JSON_SAFE },
{ "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(struct membership_data, group_name), JSON_SAFE },
{}
};
assert(!iterator->found_user_name);
assert(!iterator->found_group_name);
r = json_dispatch(parameters, dispatch_table, NULL, 0, &membership_data);
if (r < 0)
goto finish;
iterator->found_user_name = mfree(iterator->found_user_name);
iterator->found_group_name = mfree(iterator->found_group_name);
iterator->found_user_name = strdup(membership_data.user_name);
if (!iterator->found_user_name) {
r = -ENOMEM;
goto finish;
}
iterator->found_group_name = strdup(membership_data.group_name);
if (!iterator->found_group_name) {
r = -ENOMEM;
goto finish;
}
iterator->n_found++;
if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
return 0;
r = 0;
goto finish;
}
default:
assert_not_reached();
}
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)
iterator->error = -r;
assert_se(set_remove(iterator->links, link) == link);
link = varlink_unref(link);
return 0;
}
static int userdb_connect(
UserDBIterator *iterator,
const char *path,
const char *method,
bool more,
JsonVariant *query) {
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
int r;
assert(iterator);
assert(path);
assert(method);
r = varlink_connect_address(&vl, path);
if (r < 0)
return log_debug_errno(r, "Unable to connect to %s: %m", path);
varlink_set_userdata(vl, iterator);
if (!iterator->event) {
r = sd_event_new(&iterator->event);
if (r < 0)
return log_debug_errno(r, "Unable to allocate event loop: %m");
}
r = varlink_attach_event(vl, iterator->event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0)
return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m");
(void) varlink_set_description(vl, path);
r = varlink_bind_reply(vl, userdb_on_query_reply);
if (r < 0)
return log_debug_errno(r, "Failed to bind reply callback: %m");
if (more)
r = varlink_observe(vl, method, query);
else
r = varlink_invoke(vl, method, query);
if (r < 0)
return log_debug_errno(r, "Failed to invoke varlink method: %m");
r = set_ensure_consume(&iterator->links, &link_hash_ops, TAKE_PTR(vl));
if (r < 0)
return log_debug_errno(r, "Failed to add varlink connection to set: %m");
return r;
}
static int userdb_start_query(
UserDBIterator *iterator,
const char *method,
bool more,
JsonVariant *query,
UserDBFlags flags) {
_cleanup_(strv_freep) char **except = NULL, **only = NULL;
_cleanup_(closedirp) DIR *d = NULL;
struct dirent *de;
const char *e;
int r, ret = 0;
assert(iterator);
assert(method);
if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
return -ENOLINK;
e = getenv("SYSTEMD_BYPASS_USERDB");
if (e) {
r = parse_boolean(e);
if (r > 0)
return -ENOLINK;
if (r < 0) {
except = strv_split(e, ":");
if (!except)
return -ENOMEM;
}
}
e = getenv("SYSTEMD_ONLY_USERDB");
if (e) {
only = strv_split(e, ":");
if (!only)
return -ENOMEM;
}
/* First, let's talk to the multiplexer, if we can */
if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE)) == 0 &&
!strv_contains(except, "io.systemd.Multiplexer") &&
(!only || strv_contains(only, "io.systemd.Multiplexer"))) {
_cleanup_(json_variant_unrefp) JsonVariant *patched_query = json_variant_ref(query);
r = json_variant_set_field_string(&patched_query, "service", "io.systemd.Multiplexer");
if (r < 0)
return log_debug_errno(r, "Unable to set service JSON field: %m");
r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, patched_query);
if (r >= 0) {
iterator->nss_covered = true; /* The multiplexer does NSS */
iterator->dropin_covered = true; /* It also handles drop-in stuff */
return 0;
}
}
d = opendir("/run/systemd/userdb/");
if (!d) {
if (errno == ENOENT)
return -ESRCH;
return -errno;
}
FOREACH_DIRENT(de, d, return -errno) {
_cleanup_(json_variant_unrefp) JsonVariant *patched_query = NULL;
_cleanup_free_ char *p = NULL;
bool is_nss, is_dropin;
if (streq(de->d_name, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
continue;
if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
streq(de->d_name, "io.systemd.DynamicUser"))
continue;
/* Avoid NSS is this is requested. Note that we also skip NSS when we were asked to skip the
* multiplexer, since in that case it's safer to do NSS in the client side emulation below
* (and when we run as part of systemd-userdbd.service we don't want to talk to ourselves
* anyway). */
is_nss = streq(de->d_name, "io.systemd.NameServiceSwitch");
if ((flags & (USERDB_EXCLUDE_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
continue;
/* Similar for the drop-in service */
is_dropin = streq(de->d_name, "io.systemd.DropIn");
if ((flags & (USERDB_EXCLUDE_DROPIN|USERDB_AVOID_MULTIPLEXER)) && is_dropin)
continue;
if (strv_contains(except, de->d_name))
continue;
if (only && !strv_contains(only, de->d_name))
continue;
p = path_join("/run/systemd/userdb/", de->d_name);
if (!p)
return -ENOMEM;
patched_query = json_variant_ref(query);
r = json_variant_set_field_string(&patched_query, "service", de->d_name);
if (r < 0)
return log_debug_errno(r, "Unable to set service JSON field: %m");
r = userdb_connect(iterator, p, method, more, patched_query);
if (is_nss && r >= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
* and could connect to it */
iterator->nss_covered = true;
if (is_dropin && r >= 0)
iterator->dropin_covered = true;
if (ret == 0 && r < 0)
ret = r;
}
if (set_isempty(iterator->links))
return ret < 0 ? ret : -ESRCH; /* propagate last error we saw if we couldn't connect to anything. */
/* We connected to some services, in this case, ignore the ones we failed on */
return 0;
}
static int userdb_process(
UserDBIterator *iterator,
UserRecord **ret_user_record,
GroupRecord **ret_group_record,
char **ret_user_name,
char **ret_group_name) {
int r;
assert(iterator);
for (;;) {
if (iterator->what == LOOKUP_USER && iterator->found_user) {
if (ret_user_record)
*ret_user_record = TAKE_PTR(iterator->found_user);
else
iterator->found_user = user_record_unref(iterator->found_user);
if (ret_group_record)
*ret_group_record = NULL;
if (ret_user_name)
*ret_user_name = NULL;
if (ret_group_name)
*ret_group_name = NULL;
return 0;
}
if (iterator->what == LOOKUP_GROUP && iterator->found_group) {
if (ret_group_record)
*ret_group_record = TAKE_PTR(iterator->found_group);
else
iterator->found_group = group_record_unref(iterator->found_group);
if (ret_user_record)
*ret_user_record = NULL;
if (ret_user_name)
*ret_user_name = NULL;
if (ret_group_name)
*ret_group_name = NULL;
return 0;
}
if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
if (ret_user_name)
*ret_user_name = TAKE_PTR(iterator->found_user_name);
else
iterator->found_user_name = mfree(iterator->found_user_name);
if (ret_group_name)
*ret_group_name = TAKE_PTR(iterator->found_group_name);
else
iterator->found_group_name = mfree(iterator->found_group_name);
if (ret_user_record)
*ret_user_record = NULL;
if (ret_group_record)
*ret_group_record = NULL;
return 0;
}
if (set_isempty(iterator->links)) {
if (iterator->error == 0)
return -ESRCH;
return -abs(iterator->error);
}
if (!iterator->event)
return -ESRCH;
r = sd_event_run(iterator->event, UINT64_MAX);
if (r < 0)
return r;
}
}
static int synthetic_root_user_build(UserRecord **ret) {
return user_record_build(
ret,
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING("root")),
JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(0)),
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_STRING("/root")),
JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("intrinsic"))));
}
static int synthetic_nobody_user_build(UserRecord **ret) {
return user_record_build(
ret,
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(NOBODY_USER_NAME)),
JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(UID_NOBODY)),
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY)),
JSON_BUILD_PAIR("shell", JSON_BUILD_STRING(NOLOGIN)),
JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("intrinsic"))));
}
int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
int r;
if (!valid_user_group_name(name, VALID_USER_RELAX))
return -EINVAL;
r = json_build(&query, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("userName", 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)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
r = dropin_user_record_by_name(name, NULL, flags, ret);
if (r >= 0)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
/* Make sure the NSS lookup doesn't recurse back to us. */
r = userdb_iterator_block_nss_systemd(iterator);
if (r >= 0) {
/* Client-side NSS fallback */
r = nss_user_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
if (r >= 0)
return r;
}
}
if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
if (streq(name, "root"))
return synthetic_root_user_build(ret);
if (streq(name, NOBODY_USER_NAME) && synthesize_nobody())
return synthetic_nobody_user_build(ret);
}
return r;
}
int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
int r;
if (!uid_is_valid(uid))
return -EINVAL;
r = json_build(&query, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid))));
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)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
r = dropin_user_record_by_uid(uid, NULL, flags, ret);
if (r >= 0)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r >= 0) {
/* Client-side NSS fallback */
r = nss_user_record_by_uid(uid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
if (r >= 0)
return r;
}
}
if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
if (uid == 0)
return synthetic_root_user_build(ret);
if (uid == UID_NOBODY && synthesize_nobody())
return synthetic_nobody_user_build(ret);
}
return r;
}
int userdb_all(UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
int r, qr;
assert(ret);
iterator = userdb_iterator_new(LOOKUP_USER, flags);
if (!iterator)
return -ENOMEM;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", true, NULL, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r < 0)
return r;
setpwent();
iterator->nss_iterating = true;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
r = conf_files_list_nulstr(
&iterator->dropins,
".user",
NULL,
CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
USERDB_DROPIN_DIR_NULSTR("userdb"));
if (r < 0)
log_debug_errno(r, "Failed to find user drop-ins, ignoring: %m");
}
/* propagate IPC error, but only if there are no drop-ins */
if (qr < 0 &&
!iterator->nss_iterating &&
strv_isempty(iterator->dropins))
return qr;
*ret = TAKE_PTR(iterator);
return 0;
}
int userdb_iterator_get(UserDBIterator *iterator, UserRecord **ret) {
int r;
assert(iterator);
assert(iterator->what == LOOKUP_USER);
if (iterator->nss_iterating) {
struct passwd *pw;
/* If NSS isn't covered elsewhere, let's iterate through it first, since it probably contains
* the more traditional sources, which are probably good to show first. */
pw = getpwent();
if (pw) {
_cleanup_free_ char *buffer = NULL;
bool incomplete = false;
struct spwd spwd;
if (streq_ptr(pw->pw_name, "root"))
iterator->synthesize_root = false;
if (pw->pw_uid == UID_NOBODY)
iterator->synthesize_nobody = false;
if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
r = nss_spwd_for_passwd(pw, &spwd, &buffer);
if (r < 0) {
log_debug_errno(r, "Failed to acquire shadow entry for user %s, ignoring: %m", pw->pw_name);
incomplete = ERRNO_IS_PRIVILEGE(r);
}
} else {
r = -EUCLEAN;
incomplete = true;
}
r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, ret);
if (r < 0)
return r;
if (ret)
(*ret)->incomplete = incomplete;
iterator->n_found++;
return r;
}
if (errno != 0)
log_debug_errno(errno, "Failure to iterate NSS user database, ignoring: %m");
iterator->nss_iterating = false;
endpwent();
}
for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
const char *i = iterator->dropins[iterator->current_dropin];
_cleanup_free_ char *fn = NULL;
uid_t uid;
char *e;
/* Next, let's add in the static drop-ins, which are quick to retrieve */
r = path_extract_filename(i, &fn);
if (r < 0)
return r;
e = endswith(fn, ".user"); /* not actually a .user file? Then skip to next */
if (!e)
continue;
*e = 0; /* Chop off suffix */
if (parse_uid(fn, &uid) < 0) /* not a UID .user file? Then skip to next */
continue;
r = dropin_user_record_by_uid(uid, i, iterator->flags, ret);
if (r < 0) {
log_debug_errno(r, "Failed to parse user record for UID " UID_FMT ", ignoring: %m", uid);
continue; /* If we failed to parse this record, let's suppress it from enumeration,
* and continue with the next record. Maybe someone is dropping it files
* and only partially wrote this one. */
}
iterator->current_dropin++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
iterator->n_found++;
return 0;
}
/* Then, let's return the users provided by varlink IPC */
r = userdb_process(iterator, ret, NULL, NULL, NULL);
if (r < 0) {
/* Finally, synthesize root + nobody if not done yet */
if (iterator->synthesize_root) {
iterator->synthesize_root = false;
iterator->n_found++;
return synthetic_root_user_build(ret);
}
if (iterator->synthesize_nobody) {
iterator->synthesize_nobody = false;
iterator->n_found++;
return synthetic_nobody_user_build(ret);
}
/* if we found at least one entry, then ignore errors and indicate that we reached the end */
if (iterator->n_found > 0)
return -ESRCH;
}
return r;
}
static int synthetic_root_group_build(GroupRecord **ret) {
return group_record_build(
ret,
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING("root")),
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("intrinsic"))));
}
static int synthetic_nobody_group_build(GroupRecord **ret) {
return group_record_build(
ret,
JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(NOBODY_GROUP_NAME)),
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY)),
JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("intrinsic"))));
}
int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
int r;
if (!valid_user_group_name(name, VALID_USER_RELAX))
return -EINVAL;
r = json_build(&query, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name))));
if (r < 0)
return r;
iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
if (!iterator)
return -ENOMEM;
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;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && 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)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r >= 0) {
r = nss_group_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
if (r >= 0)
return r;
}
}
if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
if (streq(name, "root"))
return synthetic_root_group_build(ret);
if (streq(name, NOBODY_GROUP_NAME) && synthesize_nobody())
return synthetic_nobody_group_build(ret);
}
return r;
}
int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
int r;
if (!gid_is_valid(gid))
return -EINVAL;
r = json_build(&query, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid))));
if (r < 0)
return r;
iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
if (!iterator)
return -ENOMEM;
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;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
r = dropin_group_record_by_gid(gid, NULL, flags, ret);
if (r >= 0)
return r;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r >= 0) {
r = nss_group_record_by_gid(gid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
if (r >= 0)
return r;
}
}
if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
if (gid == 0)
return synthetic_root_group_build(ret);
if (gid == GID_NOBODY && synthesize_nobody())
return synthetic_nobody_group_build(ret);
}
return r;
}
int groupdb_all(UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
int r, qr;
assert(ret);
iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
if (!iterator)
return -ENOMEM;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", true, NULL, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r < 0)
return r;
setgrent();
iterator->nss_iterating = true;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
r = conf_files_list_nulstr(
&iterator->dropins,
".group",
NULL,
CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
USERDB_DROPIN_DIR_NULSTR("userdb"));
if (r < 0)
log_debug_errno(r, "Failed to find group drop-ins, ignoring: %m");
}
if (qr < 0 &&
!iterator->nss_iterating &&
strv_isempty(iterator->dropins))
return qr;
*ret = TAKE_PTR(iterator);
return 0;
}
int groupdb_iterator_get(UserDBIterator *iterator, GroupRecord **ret) {
int r;
assert(iterator);
assert(iterator->what == LOOKUP_GROUP);
if (iterator->nss_iterating) {
struct group *gr;
errno = 0;
gr = getgrent();
if (gr) {
_cleanup_free_ char *buffer = NULL;
bool incomplete = false;
struct sgrp sgrp;
if (streq_ptr(gr->gr_name, "root"))
iterator->synthesize_root = false;
if (gr->gr_gid == GID_NOBODY)
iterator->synthesize_nobody = false;
if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
r = nss_sgrp_for_group(gr, &sgrp, &buffer);
if (r < 0) {
log_debug_errno(r, "Failed to acquire shadow entry for group %s, ignoring: %m", gr->gr_name);
incomplete = ERRNO_IS_PRIVILEGE(r);
}
} else {
r = -EUCLEAN;
incomplete = true;
}
r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret);
if (r < 0)
return r;
if (ret)
(*ret)->incomplete = incomplete;
iterator->n_found++;
return r;
}
if (errno != 0)
log_debug_errno(errno, "Failure to iterate NSS group database, ignoring: %m");
iterator->nss_iterating = false;
endgrent();
}
for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
const char *i = iterator->dropins[iterator->current_dropin];
_cleanup_free_ char *fn = NULL;
gid_t gid;
char *e;
r = path_extract_filename(i, &fn);
if (r < 0)
return r;
e = endswith(fn, ".group");
if (!e)
continue;
*e = 0; /* Chop off suffix */
if (parse_gid(fn, &gid) < 0)
continue;
r = dropin_group_record_by_gid(gid, i, iterator->flags, ret);
if (r < 0) {
log_debug_errno(r, "Failed to parse group record for GID " GID_FMT ", ignoring: %m", gid);
continue;
}
iterator->current_dropin++;
iterator->n_found++;
return 0;
}
r = userdb_process(iterator, NULL, ret, NULL, NULL);
if (r < 0) {
if (iterator->synthesize_root) {
iterator->synthesize_root = false;
iterator->n_found++;
return synthetic_root_group_build(ret);
}
if (iterator->synthesize_nobody) {
iterator->synthesize_nobody = false;
iterator->n_found++;
return synthetic_nobody_group_build(ret);
}
/* if we found at least one entry, then ignore errors and indicate that we reached the end */
if (iterator->n_found > 0)
return -ESRCH;
}
return r;
}
static void discover_membership_dropins(UserDBIterator *i, UserDBFlags flags) {
int r;
r = conf_files_list_nulstr(
&i->dropins,
".membership",
NULL,
CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED,
USERDB_DROPIN_DIR_NULSTR("userdb"));
if (r < 0)
log_debug_errno(r, "Failed to find membership drop-ins, ignoring: %m");
}
int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
int r, qr;
assert(ret);
if (!valid_user_group_name(name, VALID_USER_RELAX))
return -EINVAL;
r = json_build(&query, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name))));
if (r < 0)
return r;
iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
if (!iterator)
return -ENOMEM;
iterator->filter_user_name = strdup(name);
if (!iterator->filter_user_name)
return -ENOMEM;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r < 0)
return r;
setgrent();
iterator->nss_iterating = true;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
discover_membership_dropins(iterator, flags);
if (qr < 0 &&
!iterator->nss_iterating &&
strv_isempty(iterator->dropins))
return qr;
*ret = TAKE_PTR(iterator);
return 0;
}
int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
int r, qr;
assert(ret);
if (!valid_user_group_name(name, VALID_USER_RELAX))
return -EINVAL;
r = json_build(&query, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name))));
if (r < 0)
return r;
iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
if (!iterator)
return -ENOMEM;
iterator->filter_group_name = strdup(name);
if (!iterator->filter_group_name)
return -ENOMEM;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
r = userdb_iterator_block_nss_systemd(iterator);
if (r < 0)
return r;
/* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
(void) nss_group_record_by_name(name, false, &gr);
if (gr) {
iterator->members_of_group = strv_copy(gr->members);
if (!iterator->members_of_group)
return -ENOMEM;
iterator->index_members_of_group = 0;
iterator->found_group_name = strdup(name);
if (!iterator->found_group_name)
return -ENOMEM;
}
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
discover_membership_dropins(iterator, flags);
if (qr < 0 &&
strv_isempty(iterator->members_of_group) &&
strv_isempty(iterator->dropins))
return qr;
*ret = TAKE_PTR(iterator);
return 0;
}
int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
int r, qr;
assert(ret);
iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
if (!iterator)
return -ENOMEM;
qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, NULL, flags);
if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
r = userdb_iterator_block_nss_systemd(iterator);
if (r < 0)
return r;
setgrent();
iterator->nss_iterating = true;
}
if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
discover_membership_dropins(iterator, flags);
if (qr < 0 &&
!iterator->nss_iterating &&
strv_isempty(iterator->dropins))
return qr;
*ret = TAKE_PTR(iterator);
return 0;
}
int membershipdb_iterator_get(
UserDBIterator *iterator,
char **ret_user,
char **ret_group) {
int r;
assert(iterator);
for (;;) {
/* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
if (!iterator->members_of_group) {
struct group *g;
if (!iterator->nss_iterating)
break;
assert(!iterator->found_user_name);
do {
errno = 0;
g = getgrent();
if (!g) {
if (errno != 0)
log_debug_errno(errno, "Failure during NSS group iteration, ignoring: %m");
break;
}
} while (iterator->filter_user_name ? !strv_contains(g->gr_mem, iterator->filter_user_name) :
strv_isempty(g->gr_mem));
if (g) {
r = free_and_strdup(&iterator->found_group_name, g->gr_name);
if (r < 0)
return r;
if (iterator->filter_user_name)
iterator->members_of_group = strv_new(iterator->filter_user_name);
else
iterator->members_of_group = strv_copy(g->gr_mem);
if (!iterator->members_of_group)
return -ENOMEM;
iterator->index_members_of_group = 0;
} else {
iterator->nss_iterating = false;
endgrent();
break;
}
}
assert(iterator->found_group_name);
assert(iterator->members_of_group);
assert(!iterator->found_user_name);
if (iterator->members_of_group[iterator->index_members_of_group]) {
_cleanup_free_ char *cu = NULL, *cg = NULL;
if (ret_user) {
cu = strdup(iterator->members_of_group[iterator->index_members_of_group]);
if (!cu)
return -ENOMEM;
}
if (ret_group) {
cg = strdup(iterator->found_group_name);
if (!cg)
return -ENOMEM;
}
if (ret_user)
*ret_user = TAKE_PTR(cu);
if (ret_group)
*ret_group = TAKE_PTR(cg);
iterator->index_members_of_group++;
return 0;
}
iterator->members_of_group = strv_free(iterator->members_of_group);
iterator->found_group_name = mfree(iterator->found_group_name);
}
for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
const char *i = iterator->dropins[iterator->current_dropin], *e, *c;
_cleanup_free_ char *un = NULL, *gn = NULL;
e = endswith(i, ".membership");
if (!e)
continue;
c = memchr(i, ':', e - i);
if (!c)
continue;
un = strndup(i, c - i);
if (!un)
return -ENOMEM;
if (iterator->filter_user_name) {
if (!streq(un, iterator->filter_user_name))
continue;
} else if (!valid_user_group_name(un, VALID_USER_RELAX))
continue;
c++; /* skip over ':' */
gn = strndup(c, e - c);
if (!gn)
return -ENOMEM;
if (iterator->filter_group_name) {
if (!streq(gn, iterator->filter_group_name))
continue;
} else if (!valid_user_group_name(gn, VALID_USER_RELAX))
continue;
iterator->current_dropin++;
iterator->n_found++;
if (ret_user)
*ret_user = TAKE_PTR(un);
if (ret_group)
*ret_group = TAKE_PTR(gn);
return 0;
}
r = userdb_process(iterator, NULL, NULL, ret_user, ret_group);
if (r < 0 && iterator->n_found > 0)
return -ESRCH;
return r;
}
int membershipdb_by_group_strv(const char *name, UserDBFlags flags, char ***ret) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
_cleanup_strv_free_ char **members = NULL;
int r;
assert(name);
assert(ret);
r = membershipdb_by_group(name, flags, &iterator);
if (r < 0)
return r;
for (;;) {
_cleanup_free_ char *user_name = NULL;
r = membershipdb_iterator_get(iterator, &user_name, NULL);
if (r == -ESRCH)
break;
if (r < 0)
return r;
r = strv_consume(&members, TAKE_PTR(user_name));
if (r < 0)
return r;
}
strv_sort(members);
strv_uniq(members);
*ret = TAKE_PTR(members);
return 0;
}
int userdb_block_nss_systemd(int b) {
_cleanup_(dlclosep) void *dl = NULL;
int (*call)(bool b);
/* Note that we might be called from libnss_systemd.so.2 itself, but that should be fine, really. */
dl = dlopen(ROOTLIBDIR "/libnss_systemd.so.2", RTLD_LAZY|RTLD_NODELETE);
if (!dl) {
/* If the file isn't installed, don't complain loudly */
log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
return 0;
}
call = (int (*)(bool b)) dlsym(dl, "_nss_systemd_block");
if (!call)
/* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD),
"Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());
return call(b);
}