e60775cb7b
They both are both short and contain similar parts and various helper will be shared between both parts of the code so it's easier to use a single file.
1250 lines
44 KiB
C
1250 lines
44 KiB
C
/* SPDX-License-Identifier: LGPL-2.1+ */
|
|
|
|
#include <sys/auxv.h>
|
|
|
|
#include "dirent-util.h"
|
|
#include "dlfcn-util.h"
|
|
#include "errno-util.h"
|
|
#include "fd-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.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;
|
|
Set *links;
|
|
bool nss_covered:1;
|
|
bool nss_iterating:1;
|
|
bool synthesize_root:1;
|
|
bool synthesize_nobody:1;
|
|
bool nss_systemd_blocked:1;
|
|
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;
|
|
};
|
|
|
|
UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
|
|
if (!iterator)
|
|
return NULL;
|
|
|
|
set_free(iterator->links);
|
|
|
|
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);
|
|
|
|
if (iterator->nss_iterating)
|
|
endgrent();
|
|
|
|
break;
|
|
|
|
default:
|
|
assert_not_reached("Unexpected state?");
|
|
}
|
|
|
|
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) {
|
|
UserDBIterator *i;
|
|
|
|
assert(what >= 0);
|
|
assert(what < _LOOKUP_WHAT_MAX);
|
|
|
|
i = new(UserDBIterator, 1);
|
|
if (!i)
|
|
return NULL;
|
|
|
|
*i = (UserDBIterator) {
|
|
.what = what,
|
|
};
|
|
|
|
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("unexpected lookup");
|
|
}
|
|
|
|
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);
|
|
|
|
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_AVOID_DYNAMIC_USER|USERDB_AVOID_NSS|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 */
|
|
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;
|
|
|
|
if (streq(de->d_name, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
|
|
continue;
|
|
|
|
if (FLAGS_SET(flags, USERDB_AVOID_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_AVOID_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
|
|
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 if we found the NSS service and could connect
|
|
* to it */
|
|
iterator->nss_covered = true;
|
|
|
|
if (ret == 0 && r < 0)
|
|
ret = r;
|
|
}
|
|
|
|
if (set_isempty(iterator->links))
|
|
return ret; /* 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);
|
|
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_AVOID_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_AVOID_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);
|
|
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_AVOID_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_AVOID_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;
|
|
|
|
assert(ret);
|
|
|
|
iterator = userdb_iterator_new(LOOKUP_USER);
|
|
if (!iterator)
|
|
return -ENOMEM;
|
|
|
|
iterator->synthesize_root = iterator->synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE);
|
|
|
|
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", true, NULL, flags);
|
|
|
|
if (!FLAGS_SET(flags, USERDB_AVOID_NSS) && (r < 0 || !iterator->nss_covered)) {
|
|
r = userdb_iterator_block_nss_systemd(iterator);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
setpwent();
|
|
iterator->nss_iterating = true;
|
|
} else if (r < 0)
|
|
return r;
|
|
|
|
*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;
|
|
|
|
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);
|
|
}
|
|
|
|
r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, ret);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (ret)
|
|
(*ret)->incomplete = incomplete;
|
|
return r;
|
|
}
|
|
|
|
if (errno != 0)
|
|
log_debug_errno(errno, "Failure to iterate NSS user database, ignoring: %m");
|
|
|
|
iterator->nss_iterating = false;
|
|
endpwent();
|
|
}
|
|
|
|
r = userdb_process(iterator, ret, NULL, NULL, NULL);
|
|
|
|
if (r < 0) {
|
|
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 (r < 0 && 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);
|
|
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_AVOID_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_AVOID_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);
|
|
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_AVOID_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_AVOID_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;
|
|
|
|
assert(ret);
|
|
|
|
iterator = userdb_iterator_new(LOOKUP_GROUP);
|
|
if (!iterator)
|
|
return -ENOMEM;
|
|
|
|
iterator->synthesize_root = iterator->synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE);
|
|
|
|
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", true, NULL, flags);
|
|
|
|
if (!FLAGS_SET(flags, USERDB_AVOID_NSS) && (r < 0 || !iterator->nss_covered)) {
|
|
r = userdb_iterator_block_nss_systemd(iterator);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
setgrent();
|
|
iterator->nss_iterating = true;
|
|
} if (r < 0)
|
|
return r;
|
|
|
|
*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;
|
|
|
|
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);
|
|
}
|
|
|
|
r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (ret)
|
|
(*ret)->incomplete = incomplete;
|
|
return r;
|
|
}
|
|
|
|
if (errno != 0)
|
|
log_debug_errno(errno, "Failure to iterate NSS group database, ignoring: %m");
|
|
|
|
iterator->nss_iterating = false;
|
|
endgrent();
|
|
}
|
|
|
|
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 (r < 0 && iterator->n_found > 0)
|
|
return -ESRCH;
|
|
|
|
return r;
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
if (!iterator)
|
|
return -ENOMEM;
|
|
|
|
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
|
|
if ((r >= 0 && iterator->nss_covered) || FLAGS_SET(flags, USERDB_AVOID_NSS))
|
|
goto finish;
|
|
|
|
r = userdb_iterator_block_nss_systemd(iterator);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
iterator->filter_user_name = strdup(name);
|
|
if (!iterator->filter_user_name)
|
|
return -ENOMEM;
|
|
|
|
setgrent();
|
|
iterator->nss_iterating = true;
|
|
|
|
r = 0;
|
|
|
|
finish:
|
|
if (r >= 0)
|
|
*ret = TAKE_PTR(iterator);
|
|
return r;
|
|
}
|
|
|
|
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;
|
|
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
|
|
int r;
|
|
|
|
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);
|
|
if (!iterator)
|
|
return -ENOMEM;
|
|
|
|
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
|
|
if ((r >= 0 && iterator->nss_covered) || FLAGS_SET(flags, USERDB_AVOID_NSS))
|
|
goto finish;
|
|
|
|
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;
|
|
}
|
|
|
|
r = 0;
|
|
|
|
finish:
|
|
if (r >= 0)
|
|
*ret = TAKE_PTR(iterator);
|
|
|
|
return r;
|
|
}
|
|
|
|
int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
|
|
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
|
|
int r;
|
|
|
|
assert(ret);
|
|
|
|
iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP);
|
|
if (!iterator)
|
|
return -ENOMEM;
|
|
|
|
r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, NULL, flags);
|
|
if ((r >= 0 && iterator->nss_covered) || FLAGS_SET(flags, USERDB_AVOID_NSS))
|
|
goto finish;
|
|
|
|
r = userdb_iterator_block_nss_systemd(iterator);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
setgrent();
|
|
iterator->nss_iterating = true;
|
|
|
|
r = 0;
|
|
|
|
finish:
|
|
if (r >= 0)
|
|
*ret = TAKE_PTR(iterator);
|
|
|
|
return r;
|
|
}
|
|
|
|
int membershipdb_iterator_get(
|
|
UserDBIterator *iterator,
|
|
char **ret_user,
|
|
char **ret_group) {
|
|
|
|
int r;
|
|
|
|
assert(iterator);
|
|
|
|
for (;;) {
|
|
/* If we are iteratring 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);
|
|
}
|
|
|
|
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 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);
|
|
}
|