7a8867abfa
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
161 lines
4.5 KiB
C
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;
|
|
}
|