Systemd/src/home/home-util.c
Lennart Poettering 7a8867abfa user-util: rework how we validate user names
This reworks the user validation infrastructure. There are now two
modes. In regular mode we are strict and test against a strict set of
valid chars. And in "relaxed" mode we just filter out some really
obvious, dangerous stuff. i.e. strict is whitelisting what is OK, but
"relaxed" is blacklisting what is really not OK.

The idea is that we use strict mode whenver we allocate a new user
(i.e. in sysusers.d or homed), while "relaxed" mode is when we process
users registered elsewhere, (i.e. userdb, logind, …)

The requirements on user name validity vary wildly. SSSD thinks its fine
to embedd "@" for example, while the suggested NAME_REGEX field on
Debian does not even allow uppercase chars…

This effectively liberaralizes a lot what we expect from usernames.

The code that warns about questionnable user names is now optional and
only used at places such as unit file parsing, so that it doesn't show
up on every userdb query, but only when processing configuration files
that know better.

Fixes: #15149 #15090
2020-04-08 17:11:20 +02:00

161 lines
4.5 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include "dns-domain.h"
#include "errno-util.h"
#include "home-util.h"
#include "libcrypt-util.h"
#include "memory-util.h"
#include "path-util.h"
#include "string-util.h"
#include "strv.h"
#include "user-util.h"
bool suitable_user_name(const char *name) {
/* Checks whether the specified name is suitable for management via homed. Note that client-side
* we usually validate with the simple valid_user_group_name(), while server-side we are a bit more
* restrictive, so that we can change the rules server-side without having to update things
* client-side too. */
if (!valid_user_group_name(name, 0))
return false;
/* We generally rely on NSS to tell us which users not to care for, but let's filter out some
* particularly well-known users. */
if (STR_IN_SET(name,
"root",
"nobody",
NOBODY_USER_NAME, NOBODY_GROUP_NAME))
return false;
/* Let's also defend our own namespace, as well as Debian's (unwritten?) logic of prefixing system
* users with underscores. */
if (STARTSWITH_SET(name, "systemd-", "_"))
return false;
return true;
}
int suitable_realm(const char *realm) {
_cleanup_free_ char *normalized = NULL;
int r;
/* Similar to the above: let's validate the realm a bit stricter server-side than client side */
r = dns_name_normalize(realm, 0, &normalized); /* this also checks general validity */
if (r == -EINVAL)
return 0;
if (r < 0)
return r;
if (!streq(realm, normalized)) /* is this normalized? */
return false;
if (dns_name_is_root(realm)) /* Don't allow top level domain */
return false;
return true;
}
int suitable_image_path(const char *path) {
return !empty_or_root(path) &&
path_is_valid(path) &&
path_is_absolute(path);
}
int split_user_name_realm(const char *t, char **ret_user_name, char **ret_realm) {
_cleanup_free_ char *user_name = NULL, *realm = NULL;
const char *c;
int r;
assert(t);
assert(ret_user_name);
assert(ret_realm);
c = strchr(t, '@');
if (!c) {
user_name = strdup(t);
if (!user_name)
return -ENOMEM;
} else {
user_name = strndup(t, c - t);
if (!user_name)
return -ENOMEM;
realm = strdup(c + 1);
if (!realm)
return -ENOMEM;
}
if (!suitable_user_name(user_name))
return -EINVAL;
if (realm) {
r = suitable_realm(realm);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
}
*ret_user_name = TAKE_PTR(user_name);
*ret_realm = TAKE_PTR(realm);
return 0;
}
int bus_message_append_secret(sd_bus_message *m, UserRecord *secret) {
_cleanup_(erase_and_freep) char *formatted = NULL;
JsonVariant *v;
int r;
assert(m);
assert(secret);
if (!FLAGS_SET(secret->mask, USER_RECORD_SECRET))
return sd_bus_message_append(m, "s", "{}");
v = json_variant_by_key(secret->json, "secret");
if (!v)
return -EINVAL;
r = json_variant_format(v, 0, &formatted);
if (r < 0)
return r;
return sd_bus_message_append(m, "s", formatted);
}
int test_password_one(const char *hashed_password, const char *password) {
struct crypt_data cc = {};
const char *k;
bool b;
errno = 0;
k = crypt_r(password, hashed_password, &cc);
if (!k) {
explicit_bzero_safe(&cc, sizeof(cc));
return errno_or_else(EINVAL);
}
b = streq(k, hashed_password);
explicit_bzero_safe(&cc, sizeof(cc));
return b;
}
int test_password_many(char **hashed_password, const char *password) {
char **hpw;
int r;
STRV_FOREACH(hpw, hashed_password) {
r = test_password_one(*hpw, password);
if (r < 0)
return r;
if (r > 0)
return true;
}
return false;
}