Files
systemd/src/shared/userdb-dropin.c
T
Lennart Poettering e931768eb4 json: rename json_append() → json_variant_merge_objectb()
json_append() is a useful wrapper around json_variant_merge(). However,
I think the naming sould be cleaned up a bit of both functions.

I thinker "merge" is the better word than "append", since it does
decidedly more than just append: it replaces existing fields of the same
name, hence "merge" sounds more appropriate. This is as opposed to the
similar operations for arrays, where no such override logic is applied
and we really just append, hence those functions are called "append"
already.

To make clearer that "merge" is about objects, and "append" about
arrays, also include "object" in the name.

Also, include "json_variant" in the name, like we do for almost all
other functions in the JSON API that take a JSON object as primary
input, and hence are kinda object methods.

Finally, let's follow the logic that helpers that combine json_build()
with some other operation get suffixed with "b" like we already have in
some cases.

Hence:

json_variant_merge() → json_variant_merge_object()
       json_append() → json_variant_merge_objectb()

This mirrors nicely the existing:
                       json_variant_append_array()
                       json_vairant_append_arrayb()

This also drops the variant of json_append() that takes a va_arg
parameter (i.e. json_appendv()). We have no user of that so far, and
given the nature as a helper function only I don#t see that happening,
and if it happens after all it's trivial to bring back.
2023-08-24 13:20:39 +02:00

305 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 "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);
}