mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
Now that we dlopen() so many deps, it might happen by accident that we end up dlopen()ening stuff when we entered a container, which we should really avoid, to not mix host and container libraries. Let's add a global variable we can set when we want to block dlopen() to ever succeed. This is then checked primarily in dlopen_many_sym_or_warn(), where we'll generate EPERM plus a log message. There are a couple of other places we invoke dlopen(), without going through dlopen_many_sym_or_warn(). This adds the same check there.
2003 lines
72 KiB
C
2003 lines
72 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||
|
||
#include <gshadow.h>
|
||
#include <stdlib.h>
|
||
|
||
#include "sd-event.h"
|
||
#include "sd-varlink.h"
|
||
|
||
#include "alloc-util.h"
|
||
#include "bitfield.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 "json-util.h"
|
||
#include "log.h"
|
||
#include "parse-util.h"
|
||
#include "set.h"
|
||
#include "string-util.h"
|
||
#include "strv.h"
|
||
#include "uid-classification.h"
|
||
#include "user-record-nss.h"
|
||
#include "user-util.h"
|
||
#include "userdb.h"
|
||
#include "userdb-dropin.h"
|
||
|
||
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(link_hash_ops, void, trivial_hash_func, trivial_compare_func, sd_varlink, sd_varlink_unref);
|
||
|
||
typedef enum LookupWhat {
|
||
LOOKUP_USER,
|
||
LOOKUP_GROUP,
|
||
LOOKUP_MEMBERSHIP,
|
||
_LOOKUP_WHAT_MAX,
|
||
} LookupWhat;
|
||
|
||
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 */
|
||
|
||
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;
|
||
};
|
||
|
||
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);
|
||
|
||
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_INTRINSIC),
|
||
.synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC),
|
||
};
|
||
|
||
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 {
|
||
sd_json_variant *record;
|
||
bool incomplete;
|
||
};
|
||
|
||
static void user_group_data_done(struct user_group_data *d) {
|
||
sd_json_variant_unref(d->record);
|
||
}
|
||
|
||
struct membership_data {
|
||
char *user_name;
|
||
char *group_name;
|
||
};
|
||
|
||
static void membership_data_done(struct membership_data *d) {
|
||
free(d->user_name);
|
||
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 suppressible 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,
|
||
const char *error_id,
|
||
sd_varlink_reply_flags_t flags,
|
||
void *userdata) {
|
||
|
||
UserDBIterator *iterator = ASSERT_PTR(userdata);
|
||
int r;
|
||
|
||
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
|
||
* completely invalid user name. */
|
||
if (STR_IN_SET(error_id,
|
||
"io.systemd.UserDatabase.NoRecordFound",
|
||
"io.systemd.UserDatabase.ConflictingRecordFound") ||
|
||
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"))
|
||
r = -EOPNOTSUPP;
|
||
else if (streq(error_id, SD_VARLINK_ERROR_TIMEOUT))
|
||
r = -ETIMEDOUT;
|
||
else
|
||
r = -EIO;
|
||
|
||
goto finish;
|
||
}
|
||
|
||
switch (iterator->what) {
|
||
|
||
case LOOKUP_USER: {
|
||
_cleanup_(user_group_data_done) struct user_group_data user_data = {};
|
||
|
||
static const sd_json_dispatch_field dispatch_table[] = {
|
||
{ "record", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant, offsetof(struct user_group_data, record), 0 },
|
||
{ "incomplete", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct user_group_data, incomplete), 0 },
|
||
{}
|
||
};
|
||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||
|
||
assert_se(!iterator->found_user);
|
||
|
||
r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &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, SD_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_done) struct user_group_data group_data = {};
|
||
|
||
static const sd_json_dispatch_field dispatch_table[] = {
|
||
{ "record", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant, offsetof(struct user_group_data, record), 0 },
|
||
{ "incomplete", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct user_group_data, incomplete), 0 },
|
||
{}
|
||
};
|
||
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
|
||
|
||
assert_se(!iterator->found_group);
|
||
|
||
r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &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, SD_VARLINK_REPLY_CONTINUES))
|
||
return 0;
|
||
|
||
r = 0;
|
||
goto finish;
|
||
}
|
||
|
||
case LOOKUP_MEMBERSHIP: {
|
||
_cleanup_(membership_data_done) struct membership_data membership_data = {};
|
||
|
||
static const sd_json_dispatch_field dispatch_table[] = {
|
||
{ "userName", SD_JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(struct membership_data, user_name), SD_JSON_RELAX },
|
||
{ "groupName", SD_JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(struct membership_data, group_name), SD_JSON_RELAX },
|
||
{}
|
||
};
|
||
|
||
assert(!iterator->found_user_name);
|
||
assert(!iterator->found_group_name);
|
||
|
||
r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &membership_data);
|
||
if (r < 0)
|
||
goto finish;
|
||
|
||
iterator->found_user_name = TAKE_PTR(membership_data.user_name);
|
||
iterator->found_group_name = TAKE_PTR(membership_data.group_name);
|
||
iterator->n_found++;
|
||
|
||
if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
|
||
return 0;
|
||
|
||
r = 0;
|
||
goto finish;
|
||
}
|
||
|
||
default:
|
||
assert_not_reached();
|
||
}
|
||
|
||
finish:
|
||
/* If we got one ENOEXEC, let that win. Similarly, ESRCH wins except for ENOEXEC. 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 (r == -ENOEXEC ||
|
||
(r == -ESRCH && iterator->error != ENOEXEC) ||
|
||
iterator->error == 0)
|
||
iterator->error = -r;
|
||
|
||
assert_se(set_remove(iterator->links, link) == link);
|
||
link = sd_varlink_unref(link);
|
||
return 0;
|
||
}
|
||
|
||
static int userdb_connect(
|
||
UserDBIterator *iterator,
|
||
const char *path,
|
||
const char *method,
|
||
bool more,
|
||
sd_json_variant *query) {
|
||
|
||
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
|
||
int r;
|
||
|
||
assert(iterator);
|
||
assert(path);
|
||
assert(method);
|
||
|
||
r = sd_varlink_connect_address(&vl, path);
|
||
if (r < 0)
|
||
return log_debug_errno(r, "Unable to connect to %s: %m", path);
|
||
|
||
sd_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 = sd_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");
|
||
|
||
/* 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)
|
||
return log_debug_errno(r, "Failed to bind reply callback: %m");
|
||
|
||
_cleanup_free_ char *service = NULL;
|
||
r = path_extract_filename(path, &service);
|
||
if (r < 0)
|
||
return log_debug_errno(r, "Failed to extract service name from socket path: %m");
|
||
assert(r != O_DIRECTORY);
|
||
|
||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query = sd_json_variant_ref(query);
|
||
r = sd_json_variant_set_field_string(&patched_query, "service", service);
|
||
if (r < 0)
|
||
return log_debug_errno(r, "Unable to set service JSON field: %m");
|
||
|
||
if (more)
|
||
r = sd_varlink_observe(vl, method, patched_query);
|
||
else
|
||
r = sd_varlink_invoke(vl, method, patched_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, /* must be a static string, we are not going to copy this here! */
|
||
bool more,
|
||
sd_json_variant *query,
|
||
UserDBFlags flags) {
|
||
|
||
_cleanup_strv_free_ char **except = NULL, **only = NULL;
|
||
_cleanup_closedir_ DIR *d = NULL;
|
||
const char *e;
|
||
int r, ret = 0;
|
||
|
||
assert(iterator);
|
||
assert(method);
|
||
|
||
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);
|
||
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_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN)) == 0 &&
|
||
!strv_contains(except, "io.systemd.Multiplexer") &&
|
||
(!only || strv_contains(only, "io.systemd.Multiplexer"))) {
|
||
r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, 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_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 if 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;
|
||
|
||
r = userdb_connect(iterator, p, method, more, 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;
|
||
|
||
RET_GATHER(ret, r);
|
||
}
|
||
|
||
if (set_isempty(iterator->links))
|
||
return ret < 0 ? ret : -ESRCH; /* propagate the first 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_buildo(
|
||
ret,
|
||
SD_JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING("root")),
|
||
SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(0)),
|
||
SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(0)),
|
||
SD_JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/root")),
|
||
SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic")));
|
||
}
|
||
|
||
static int synthetic_nobody_user_build(UserRecord **ret) {
|
||
return user_record_buildo(
|
||
ret,
|
||
SD_JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING(NOBODY_USER_NAME)),
|
||
SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(UID_NOBODY)),
|
||
SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(GID_NOBODY)),
|
||
SD_JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN)),
|
||
SD_JSON_BUILD_PAIR("locked", SD_JSON_BUILD_BOOLEAN(true)),
|
||
SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic")));
|
||
}
|
||
|
||
static int synthetic_foreign_user_build(uid_t foreign_uid, UserRecord **ret) {
|
||
assert(ret);
|
||
|
||
if (!uid_is_valid(foreign_uid))
|
||
return -ESRCH;
|
||
if (foreign_uid > 0xFFFF)
|
||
return -ESRCH;
|
||
|
||
_cleanup_free_ char *un = NULL;
|
||
if (asprintf(&un, "foreign-" UID_FMT, foreign_uid) < 0)
|
||
return -ENOMEM;
|
||
|
||
_cleanup_free_ char *rn = NULL;
|
||
if (asprintf(&rn, "Foreign System Image UID " UID_FMT, foreign_uid) < 0)
|
||
return -ENOMEM;
|
||
|
||
return user_record_buildo(
|
||
ret,
|
||
SD_JSON_BUILD_PAIR_STRING("userName", un),
|
||
SD_JSON_BUILD_PAIR_STRING("realName", rn),
|
||
SD_JSON_BUILD_PAIR_UNSIGNED("uid", FOREIGN_UID_BASE + foreign_uid),
|
||
SD_JSON_BUILD_PAIR_UNSIGNED("gid", FOREIGN_UID_BASE + foreign_uid),
|
||
SD_JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN)),
|
||
SD_JSON_BUILD_PAIR_BOOLEAN("locked", true),
|
||
SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign")));
|
||
}
|
||
|
||
static int synthetic_numeric_user_build(uid_t uid, UserRecord **ret) {
|
||
assert(ret);
|
||
|
||
if (uid == 0) /* This should be handled by synthetic_root_user_build() */
|
||
return -ESRCH;
|
||
|
||
if (!uid_is_system(uid))
|
||
return -ESRCH;
|
||
|
||
_cleanup_free_ char *un = NULL;
|
||
if (asprintf(&un, "unknown-" UID_FMT, uid) < 0)
|
||
return -ENOMEM;
|
||
|
||
_cleanup_free_ char *rn = NULL;
|
||
if (asprintf(&rn, "Unknown System UID " UID_FMT, uid) < 0)
|
||
return -ENOMEM;
|
||
|
||
return user_record_buildo(
|
||
ret,
|
||
SD_JSON_BUILD_PAIR_STRING("userName", un),
|
||
SD_JSON_BUILD_PAIR_STRING("realName", rn),
|
||
SD_JSON_BUILD_PAIR_UNSIGNED("uid", uid),
|
||
SD_JSON_BUILD_PAIR_STRING("disposition", "system"));
|
||
}
|
||
|
||
static int user_name_foreign_extract_uid(const char *name, uid_t *ret_uid) {
|
||
int r;
|
||
|
||
assert(name);
|
||
assert(ret_uid);
|
||
|
||
/* Parses the inner UID from a user name of the foreign UID range, in the form "foreign-NNN". Returns
|
||
* > 0 if that worked, 0 if it didn't. */
|
||
|
||
const char *e = startswith(name, "foreign-");
|
||
if (!e)
|
||
goto nomatch;
|
||
|
||
uid_t uid;
|
||
r = parse_uid(e, &uid);
|
||
if (r < 0)
|
||
goto nomatch;
|
||
|
||
if (uid > 0xFFFF)
|
||
goto nomatch;
|
||
|
||
*ret_uid = uid;
|
||
return 1;
|
||
|
||
nomatch:
|
||
*ret_uid = UID_INVALID;
|
||
return 0;
|
||
}
|
||
|
||
static int query_append_common(sd_json_variant **query, const UserDBMatch *match) {
|
||
int r;
|
||
_cleanup_strv_free_ char **dispositions = NULL;
|
||
|
||
assert(query);
|
||
|
||
uint64_t mask = match->disposition_mask;
|
||
if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL)) {
|
||
for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
|
||
if (!BIT_SET(mask, d))
|
||
continue;
|
||
|
||
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_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
|
||
SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(match->uuid), "uuid", SD_JSON_BUILD_UUID(match->uuid)),
|
||
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(dispositions), "dispositionMask", SD_JSON_BUILD_STRV(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(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_common(query, match);
|
||
}
|
||
|
||
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, /* path= */ 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_INTRINSIC)) {
|
||
if (streq(name, "root"))
|
||
return synthetic_root_user_build(ret);
|
||
|
||
if (streq(name, NOBODY_USER_NAME) && synthesize_nobody())
|
||
return synthetic_nobody_user_build(ret);
|
||
}
|
||
|
||
if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
|
||
uid_t foreign_uid;
|
||
r = user_name_foreign_extract_uid(name, &foreign_uid);
|
||
if (r < 0)
|
||
return r;
|
||
if (r > 0)
|
||
return synthetic_foreign_user_build(foreign_uid, ret);
|
||
}
|
||
|
||
return -ESRCH;
|
||
}
|
||
|
||
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;
|
||
|
||
/* 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_STRING("userName", name));
|
||
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;
|
||
|
||
_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) /* 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. */
|
||
if (!user_record_match(ur, match))
|
||
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);
|
||
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_INTRINSIC)) {
|
||
if (uid == 0)
|
||
return synthetic_root_user_build(ret);
|
||
|
||
if (uid == UID_NOBODY && synthesize_nobody())
|
||
return synthetic_nobody_user_build(ret);
|
||
}
|
||
|
||
if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && uid_is_foreign(uid))
|
||
return synthetic_foreign_user_build(uid - FOREIGN_UID_BASE, ret);
|
||
|
||
if (FLAGS_SET(flags, USERDB_SYNTHESIZE_NUMERIC))
|
||
return synthetic_numeric_user_build(uid, ret);
|
||
|
||
return -ESRCH;
|
||
}
|
||
|
||
int userdb_by_uid(uid_t uid, 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))
|
||
return -EINVAL;
|
||
|
||
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR_UNSIGNED("uid", 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;
|
||
|
||
_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;
|
||
}
|
||
|
||
if (!user_record_match(ur, match))
|
||
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);
|
||
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");
|
||
}
|
||
|
||
/* Note that we do not enumerate the foreign users, since those would be just 64K of noise */
|
||
|
||
/* 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;
|
||
}
|
||
|
||
static int userdb_iterator_get_one(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. */
|
||
|
||
errno = 0;
|
||
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, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ 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;
|
||
}
|
||
|
||
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_buildo(
|
||
ret,
|
||
SD_JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING("root")),
|
||
SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(0)),
|
||
SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic")));
|
||
}
|
||
|
||
static int synthetic_nobody_group_build(GroupRecord **ret) {
|
||
return group_record_buildo(
|
||
ret,
|
||
SD_JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING(NOBODY_GROUP_NAME)),
|
||
SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(GID_NOBODY)),
|
||
SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic")));
|
||
}
|
||
|
||
static int synthetic_foreign_group_build(gid_t foreign_gid, GroupRecord **ret) {
|
||
assert(ret);
|
||
|
||
if (!gid_is_valid(foreign_gid))
|
||
return -ESRCH;
|
||
if (foreign_gid > 0xFFFF)
|
||
return -ESRCH;
|
||
|
||
_cleanup_free_ char *gn = NULL;
|
||
if (asprintf(&gn, "foreign-" GID_FMT, foreign_gid) < 0)
|
||
return -ENOMEM;
|
||
|
||
_cleanup_free_ char *d = NULL;
|
||
if (asprintf(&d, "Foreign System Image GID " GID_FMT, foreign_gid) < 0)
|
||
return -ENOMEM;
|
||
|
||
return group_record_buildo(
|
||
ret,
|
||
SD_JSON_BUILD_PAIR_STRING("groupName", gn),
|
||
SD_JSON_BUILD_PAIR_STRING("description", d),
|
||
SD_JSON_BUILD_PAIR_UNSIGNED("gid", FOREIGN_UID_BASE + foreign_gid),
|
||
SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign")));
|
||
}
|
||
|
||
static int synthetic_numeric_group_build(gid_t gid, GroupRecord **ret) {
|
||
assert(ret);
|
||
|
||
if (gid == 0) /* This should be handled by synthetic_root_group_build() */
|
||
return -ESRCH;
|
||
|
||
if (!gid_is_system(gid))
|
||
return -ESRCH;
|
||
|
||
_cleanup_free_ char *gn = NULL;
|
||
if (asprintf(&gn, "unknown-" GID_FMT, gid) < 0)
|
||
return -ENOMEM;
|
||
|
||
_cleanup_free_ char *d = NULL;
|
||
if (asprintf(&d, "Unknown System GID " UID_FMT, gid) < 0)
|
||
return -ENOMEM;
|
||
|
||
return group_record_buildo(
|
||
ret,
|
||
SD_JSON_BUILD_PAIR_STRING("groupName", gn),
|
||
SD_JSON_BUILD_PAIR_STRING("description", d),
|
||
SD_JSON_BUILD_PAIR_UNSIGNED("gid", gid),
|
||
SD_JSON_BUILD_PAIR_STRING("disposition", "system"));
|
||
}
|
||
|
||
static int query_append_gid_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(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;
|
||
|
||
return query_append_common(query, match);
|
||
}
|
||
|
||
static int groupdb_by_name_fallbacks(
|
||
const char *name,
|
||
UserDBIterator *iterator,
|
||
UserDBFlags flags,
|
||
GroupRecord **ret) {
|
||
|
||
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->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_INTRINSIC)) {
|
||
if (streq(name, "root"))
|
||
return synthetic_root_group_build(ret);
|
||
|
||
if (streq(name, NOBODY_GROUP_NAME) && synthesize_nobody())
|
||
return synthetic_nobody_group_build(ret);
|
||
}
|
||
|
||
if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
|
||
uid_t foreign_gid;
|
||
r = user_name_foreign_extract_uid(name, &foreign_gid); /* Same for UID + GID */
|
||
if (r < 0)
|
||
return r;
|
||
if (r > 0)
|
||
return synthetic_foreign_group_build(foreign_gid, ret);
|
||
}
|
||
|
||
return -ESRCH;
|
||
}
|
||
|
||
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;
|
||
|
||
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_STRING("groupName", name));
|
||
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;
|
||
|
||
_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_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. */
|
||
if (!group_record_match(gr, match))
|
||
return -ENOEXEC;
|
||
|
||
if (ret)
|
||
*ret = TAKE_PTR(gr);
|
||
|
||
return 0;
|
||
}
|
||
|
||
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);
|
||
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_INTRINSIC)) {
|
||
if (gid == 0)
|
||
return synthetic_root_group_build(ret);
|
||
|
||
if (gid == GID_NOBODY && synthesize_nobody())
|
||
return synthetic_nobody_group_build(ret);
|
||
}
|
||
|
||
if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && gid_is_foreign(gid))
|
||
return synthetic_foreign_group_build(gid - FOREIGN_UID_BASE, ret);
|
||
|
||
if (FLAGS_SET(flags, USERDB_SYNTHESIZE_NUMERIC))
|
||
return synthetic_numeric_group_build(gid, ret);
|
||
|
||
return -ESRCH;
|
||
}
|
||
|
||
int groupdb_by_gid(gid_t gid, 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))
|
||
return -EINVAL;
|
||
|
||
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR_UNSIGNED("gid", 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;
|
||
|
||
_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;
|
||
}
|
||
|
||
if (!group_record_match(gr, match))
|
||
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);
|
||
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;
|
||
}
|
||
|
||
static int groupdb_iterator_get_one(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;
|
||
}
|
||
|
||
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;
|
||
|
||
r = conf_files_list_nulstr(
|
||
&i->dropins,
|
||
".membership",
|
||
NULL,
|
||
CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED_BY_SYMLINK,
|
||
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_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
|
||
int r, qr;
|
||
|
||
assert(ret);
|
||
|
||
if (!valid_user_group_name(name, VALID_USER_RELAX))
|
||
return -EINVAL;
|
||
|
||
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR_STRING("userName", 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_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
|
||
int r, qr;
|
||
|
||
assert(ret);
|
||
|
||
if (!valid_user_group_name(name, VALID_USER_RELAX))
|
||
return -EINVAL;
|
||
|
||
r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR_STRING("groupName", 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_uniq(members);
|
||
|
||
*ret = TAKE_PTR(members);
|
||
return 0;
|
||
}
|
||
|
||
int userdb_block_nss_systemd(int b) {
|
||
int r;
|
||
|
||
/* Note that we might be called from libnss_systemd.so.2 itself, but that should be fine, really. */
|
||
|
||
_cleanup_(dlclosep) void *dl = NULL;
|
||
const char *dle;
|
||
r = dlopen_safe(LIBDIR "/libnss_systemd.so.2", &dl, &dle);
|
||
if (r < 0) {
|
||
/* If the file isn't installed, don't complain loudly */
|
||
log_debug_errno(r, "Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dle ?: STRERROR(r));
|
||
return 0;
|
||
}
|
||
|
||
log_debug("Loaded '%s' via dlopen()", LIBDIR "/libnss_systemd.so.2");
|
||
|
||
int (*call)(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);
|
||
}
|