mirror of
https://github.com/systemd/systemd.git
synced 2026-07-25 00:35:55 +00:00
311 lines
10 KiB
C
311 lines
10 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "sd-json.h"
|
|
|
|
#include "alloc-util.h"
|
|
#include "errno-util.h"
|
|
#include "fd-util.h"
|
|
#include "fileio.h"
|
|
#include "format-util.h"
|
|
#include "group-record.h"
|
|
#include "path-util.h"
|
|
#include "stdio-util.h"
|
|
#include "string-util.h"
|
|
#include "user-util.h"
|
|
#include "userdb.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);
|
|
}
|