/* 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_(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 && !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_(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 && !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); }