diff options
Diffstat (limited to 'src/shared/userdb-dropin.c')
-rw-r--r-- | src/shared/userdb-dropin.c | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/src/shared/userdb-dropin.c b/src/shared/userdb-dropin.c new file mode 100644 index 0000000..a2d48fa --- /dev/null +++ b/src/shared/userdb-dropin.c @@ -0,0 +1,304 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-util.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_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_(user_record_unrefp) UserRecord *u = NULL; + bool have_privileged; + int r; + + assert(f); + + r = 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_(json_variant_unrefp) JsonVariant *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 = json_parse_file(NULL, j, 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 = 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 && !streq_ptr(name, u->user_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_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_(group_record_unrefp) GroupRecord *g = NULL; + bool have_privileged; + int r; + + assert(f); + + r = 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_(json_variant_unrefp) JsonVariant *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 = json_parse_file(NULL, j, 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 = 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 && !streq_ptr(name, g->group_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); +} |