summaryrefslogtreecommitdiffstats
path: root/src/shared/userdb-dropin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared/userdb-dropin.c')
-rw-r--r--src/shared/userdb-dropin.c304
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);
+}