Files
systemd/src/shared/userdb-dropin.c
T
Daan De Meyer 93a1f7921a basic: Stop including log.h in macro.h
Now that the necessary functions from log.h have been moved to macro.h,
we can stop including log.h in macro.h. This requires modifying source
files all over the tree to include log.h instead.
2025-04-18 14:19:15 +02:00

307 lines
10 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "group-record.h"
#include "log.h"
#include "path-util.h"
#include "stdio-util.h"
#include "user-util.h"
#include "userdb-dropin.h"
static int load_user(
FILE *f,
const char *path,
const char *name,
uid_t uid,
UserDBFlags flags,
UserRecord **ret) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_(user_record_unrefp) UserRecord *u = NULL;
bool have_privileged;
int r;
assert(f);
r = sd_json_parse_file(f, path, 0, &v, NULL, NULL);
if (r < 0)
return r;
if (FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW) || !path || !(name || uid_is_valid(uid)))
have_privileged = false;
else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *privileged_v = NULL;
_cleanup_free_ char *d = NULL, *j = NULL;
/* Let's load the "privileged" section from a companion file. But only if USERDB_AVOID_SHADOW
* is not set. After all, the privileged section kinda takes the role of the data from the
* shadow file, hence it makes sense to use the same flag here.
*
* The general assumption is that whoever provides these records makes the .user file
* world-readable, but the .privilege file readable to root and the assigned UID only. But we
* won't verify that here, as it would be too late. */
r = path_extract_directory(path, &d);
if (r < 0)
return r;
if (name) {
j = strjoin(d, "/", name, ".user-privileged");
if (!j)
return -ENOMEM;
} else {
assert(uid_is_valid(uid));
if (asprintf(&j, "%s/" UID_FMT ".user-privileged", d, uid) < 0)
return -ENOMEM;
}
r = sd_json_parse_file(NULL, j, SD_JSON_PARSE_SENSITIVE, &privileged_v, NULL, NULL);
if (ERRNO_IS_NEG_PRIVILEGE(r))
have_privileged = false;
else if (r == -ENOENT)
have_privileged = true; /* if the privileged file doesn't exist, we are complete */
else if (r < 0)
return r;
else {
r = sd_json_variant_merge_object(&v, privileged_v);
if (r < 0)
return r;
have_privileged = true;
}
}
u = user_record_new();
if (!u)
return -ENOMEM;
r = user_record_load(
u, v,
USER_RECORD_REQUIRE_REGULAR|
USER_RECORD_ALLOW_PER_MACHINE|
USER_RECORD_ALLOW_BINDING|
USER_RECORD_ALLOW_SIGNATURE|
(have_privileged ? USER_RECORD_ALLOW_PRIVILEGED : 0)|
USER_RECORD_PERMISSIVE);
if (r < 0)
return r;
if (name && !user_record_matches_user_name(u, name))
return -EINVAL;
if (uid_is_valid(uid) && uid != u->uid)
return -EINVAL;
u->incomplete = !have_privileged;
if (ret)
*ret = TAKE_PTR(u);
return 0;
}
int dropin_user_record_by_name(const char *name, const char *path, UserDBFlags flags, UserRecord **ret) {
_cleanup_free_ char *found_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(name);
if (path) {
f = fopen(path, "re");
if (!f)
return errno == ENOENT ? -ESRCH : -errno; /* We generally want ESRCH to indicate no such user */
} else {
const char *j;
j = strjoina(name, ".user");
if (!filename_is_valid(j)) /* Doesn't qualify as valid filename? Then it's definitely not provided as a drop-in */
return -ESRCH;
r = search_and_fopen_nulstr(j, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
if (r == -ENOENT)
return -ESRCH;
if (r < 0)
return r;
path = found_path;
}
return load_user(f, path, name, UID_INVALID, flags, ret);
}
int dropin_user_record_by_uid(uid_t uid, const char *path, UserDBFlags flags, UserRecord **ret) {
_cleanup_free_ char *found_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(uid_is_valid(uid));
if (path) {
f = fopen(path, "re");
if (!f)
return errno == ENOENT ? -ESRCH : -errno;
} else {
char buf[DECIMAL_STR_MAX(uid_t) + STRLEN(".user") + 1];
xsprintf(buf, UID_FMT ".user", uid);
/* Note that we don't bother to validate this as a filename, as this is generated from a decimal
* integer, i.e. is definitely OK as a filename */
r = search_and_fopen_nulstr(buf, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
if (r == -ENOENT)
return -ESRCH;
if (r < 0)
return r;
path = found_path;
}
return load_user(f, path, NULL, uid, flags, ret);
}
static int load_group(
FILE *f,
const char *path,
const char *name,
gid_t gid,
UserDBFlags flags,
GroupRecord **ret) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
bool have_privileged;
int r;
assert(f);
r = sd_json_parse_file(f, path, 0, &v, NULL, NULL);
if (r < 0)
return r;
if (FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW) || !path || !(name || gid_is_valid(gid)))
have_privileged = false;
else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *privileged_v = NULL;
_cleanup_free_ char *d = NULL, *j = NULL;
r = path_extract_directory(path, &d);
if (r < 0)
return r;
if (name) {
j = strjoin(d, "/", name, ".group-privileged");
if (!j)
return -ENOMEM;
} else {
assert(gid_is_valid(gid));
if (asprintf(&j, "%s/" GID_FMT ".group-privileged", d, gid) < 0)
return -ENOMEM;
}
r = sd_json_parse_file(NULL, j, SD_JSON_PARSE_SENSITIVE, &privileged_v, NULL, NULL);
if (ERRNO_IS_NEG_PRIVILEGE(r))
have_privileged = false;
else if (r == -ENOENT)
have_privileged = true; /* if the privileged file doesn't exist, we are complete */
else if (r < 0)
return r;
else {
r = sd_json_variant_merge_object(&v, privileged_v);
if (r < 0)
return r;
have_privileged = true;
}
}
g = group_record_new();
if (!g)
return -ENOMEM;
r = group_record_load(
g, v,
USER_RECORD_REQUIRE_REGULAR|
USER_RECORD_ALLOW_PER_MACHINE|
USER_RECORD_ALLOW_BINDING|
USER_RECORD_ALLOW_SIGNATURE|
(have_privileged ? USER_RECORD_ALLOW_PRIVILEGED : 0)|
USER_RECORD_PERMISSIVE);
if (r < 0)
return r;
if (name && !group_record_matches_group_name(g, name))
return -EINVAL;
if (gid_is_valid(gid) && gid != g->gid)
return -EINVAL;
g->incomplete = !have_privileged;
if (ret)
*ret = TAKE_PTR(g);
return 0;
}
int dropin_group_record_by_name(const char *name, const char *path, UserDBFlags flags, GroupRecord **ret) {
_cleanup_free_ char *found_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(name);
if (path) {
f = fopen(path, "re");
if (!f)
return errno == ENOENT ? -ESRCH : -errno;
} else {
const char *j;
j = strjoina(name, ".group");
if (!filename_is_valid(j)) /* Doesn't qualify as valid filename? Then it's definitely not provided as a drop-in */
return -ESRCH;
r = search_and_fopen_nulstr(j, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
if (r == -ENOENT)
return -ESRCH;
if (r < 0)
return r;
path = found_path;
}
return load_group(f, path, name, GID_INVALID, flags, ret);
}
int dropin_group_record_by_gid(gid_t gid, const char *path, UserDBFlags flags, GroupRecord **ret) {
_cleanup_free_ char *found_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(gid_is_valid(gid));
if (path) {
f = fopen(path, "re");
if (!f)
return errno == ENOENT ? -ESRCH : -errno;
} else {
char buf[DECIMAL_STR_MAX(gid_t) + STRLEN(".group") + 1];
xsprintf(buf, GID_FMT ".group", gid);
r = search_and_fopen_nulstr(buf, "re", NULL, USERDB_DROPIN_DIR_NULSTR("userdb"), &f, &found_path);
if (r == -ENOENT)
return -ESRCH;
if (r < 0)
return r;
path = found_path;
}
return load_group(f, path, NULL, gid, flags, ret);
}