8be66a
From 33b851f0c30e47fe71a293e2c990ef26573efe86 Mon Sep 17 00:00:00 2001
8be66a
From: Lennart Poettering <lennart@poettering.net>
8be66a
Date: Sat, 4 Apr 2020 12:23:02 +0200
8be66a
Subject: [PATCH] user-util: rework how we validate user names
8be66a
MIME-Version: 1.0
8be66a
Content-Type: text/plain; charset=UTF-8
8be66a
Content-Transfer-Encoding: 8bit
8be66a
8be66a
This reworks the user validation infrastructure. There are now two
8be66a
modes. In regular mode we are strict and test against a strict set of
8be66a
valid chars. And in "relaxed" mode we just filter out some really
8be66a
obvious, dangerous stuff. i.e. strict is whitelisting what is OK, but
8be66a
"relaxed" is blacklisting what is really not OK.
8be66a
8be66a
The idea is that we use strict mode whenver we allocate a new user
8be66a
(i.e. in sysusers.d or homed), while "relaxed" mode is when we process
8be66a
users registered elsewhere, (i.e. userdb, logind, …)
8be66a
8be66a
The requirements on user name validity vary wildly. SSSD thinks its fine
8be66a
to embedd "@" for example, while the suggested NAME_REGEX field on
8be66a
Debian does not even allow uppercase chars…
8be66a
8be66a
This effectively liberaralizes a lot what we expect from usernames.
8be66a
8be66a
The code that warns about questionnable user names is now optional and
8be66a
only used at places such as unit file parsing, so that it doesn't show
8be66a
up on every userdb query, but only when processing configuration files
8be66a
that know better.
8be66a
8be66a
Fixes: #15149 #15090
8be66a
(cherry picked from commit 7a8867abfab10e5bbca10590ec2aa40c5b27d8fb)
8be66a
8be66a
Resolves: #1848373
8be66a
---
8be66a
 src/basic/user-util.c         | 185 +++++++++++++----------
8be66a
 src/basic/user-util.h         |  21 +--
8be66a
 src/core/dbus-execute.c       |   6 +-
8be66a
 src/core/dbus-manager.c       |   2 +-
8be66a
 src/core/dbus-socket.c        |   4 +-
8be66a
 src/core/dbus-util.c          |   7 +-
8be66a
 src/core/dbus-util.h          |   2 +-
8be66a
 src/core/dynamic-user.c       |   2 +-
8be66a
 src/core/load-fragment.c      |   4 +-
8be66a
 src/core/unit.c               |   2 +-
8be66a
 src/nss-systemd/nss-systemd.c |   6 +-
8be66a
 src/systemd/sd-messages.h     |   3 +
8be66a
 src/sysusers/sysusers.c       |   4 +-
8be66a
 src/test/test-user-util.c     | 271 ++++++++++++++++++----------------
8be66a
 14 files changed, 287 insertions(+), 232 deletions(-)
8be66a
8be66a
diff --git a/src/basic/user-util.c b/src/basic/user-util.c
8be66a
index 68a924770b..cd870c4361 100644
8be66a
--- a/src/basic/user-util.c
8be66a
+++ b/src/basic/user-util.c
8be66a
@@ -14,6 +14,8 @@
8be66a
 #include <unistd.h>
8be66a
 #include <utmp.h>
8be66a
 
8be66a
+#include "sd-messages.h"
8be66a
+
8be66a
 #include "alloc-util.h"
8be66a
 #include "fd-util.h"
8be66a
 #include "fileio.h"
8be66a
@@ -576,92 +578,125 @@ int take_etc_passwd_lock(const char *root) {
8be66a
         return fd;
8be66a
 }
8be66a
 
8be66a
-bool valid_user_group_name_full(const char *u, bool strict) {
8be66a
+bool valid_user_group_name(const char *u, ValidUserFlags flags) {
8be66a
         const char *i;
8be66a
-        long sz;
8be66a
-        bool warned = false;
8be66a
 
8be66a
-        /* Checks if the specified name is a valid user/group name. Also see POSIX IEEE Std 1003.1-2008, 2016 Edition,
8be66a
-         * 3.437. We are a bit stricter here however. Specifically we deviate from POSIX rules:
8be66a
-         *
8be66a
-         * - We require that names fit into the appropriate utmp field
8be66a
-         * - We don't allow empty user names
8be66a
-         * - No dots in the first character
8be66a
+        /* Checks if the specified name is a valid user/group name. There are two flavours of this call:
8be66a
+         * strict mode is the default which is POSIX plus some extra rules; and relaxed mode where we accept
8be66a
+         * pretty much everything except the really worst offending names.
8be66a
          *
8be66a
-         * If strict==true, additionally:
8be66a
-         * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator)
8be66a
-         * - We don't allow a digit as the first character
8be66a
-         *
8be66a
-         * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
8be66a
-         */
8be66a
+         * Whenever we synthesize users ourselves we should use the strict mode. But when we process users
8be66a
+         * created by other stuff, let's be more liberal. */
8be66a
 
8be66a
-        if (isempty(u))
8be66a
+        if (isempty(u)) /* An empty user name is never valid */
8be66a
                 return false;
8be66a
 
8be66a
-        if (!(u[0] >= 'a' && u[0] <= 'z') &&
8be66a
-            !(u[0] >= 'A' && u[0] <= 'Z') &&
8be66a
-            !(u[0] >= '0' && u[0] <= '9' && !strict) &&
8be66a
-            u[0] != '_')
8be66a
-                return false;
8be66a
-
8be66a
-        bool only_digits_seen = u[0] >= '0' && u[0] <= '9';
8be66a
-
8be66a
-        if (only_digits_seen) {
8be66a
-                log_warning("User or group name \"%s\" starts with a digit, accepting for compatibility.", u);
8be66a
-                warned = true;
8be66a
-        }
8be66a
-
8be66a
-        for (i = u+1; *i; i++) {
8be66a
-                if (((*i >= 'a' && *i <= 'z') ||
8be66a
-                     (*i >= 'A' && *i <= 'Z') ||
8be66a
-                     (*i >= '0' && *i <= '9') ||
8be66a
-                     IN_SET(*i, '_', '-'))) {
8be66a
-                        if (!(*i >= '0' && *i <= '9'))
8be66a
-                                only_digits_seen = false;
8be66a
-                        continue;
8be66a
-                        }
8be66a
-
8be66a
-                if (*i == '.' && !strict) {
8be66a
-                        if (!warned) {
8be66a
-                                log_warning("Bad user or group name \"%s\", accepting for compatibility.", u);
8be66a
-                                warned = true;
8be66a
-                        }
8be66a
-
8be66a
-                        continue;
8be66a
-                }
8be66a
-
8be66a
-                return false;
8be66a
+        if (parse_uid(u, NULL) >= 0) /* Something that parses as numeric UID string is valid exactly when the
8be66a
+                                      * flag for it is set */
8be66a
+                return FLAGS_SET(flags, VALID_USER_ALLOW_NUMERIC);
8be66a
+
8be66a
+        if (FLAGS_SET(flags, VALID_USER_RELAX)) {
8be66a
+
8be66a
+                /* In relaxed mode we just check very superficially. Apparently SSSD and other stuff is
8be66a
+                 * extremely liberal (way too liberal if you ask me, even inserting "@" in user names, which
8be66a
+                 * is bound to cause problems for example when used with an MTA), hence only filter the most
8be66a
+                 * obvious cases, or where things would result in an invalid entry if such a user name would
8be66a
+                 * show up in /etc/passwd (or equivalent getent output).
8be66a
+                 *
8be66a
+                 * Note that we stepped far out of POSIX territory here. It's not our fault though, but
8be66a
+                 * SSSD's, Samba's and everybody else who ignored POSIX on this. (I mean, I am happy to step
8be66a
+                 * outside of POSIX' bounds any day, but I must say in this case I probably wouldn't
8be66a
+                 * have...) */
8be66a
+
8be66a
+                if (startswith(u, " ") || endswith(u, " ")) /* At least expect whitespace padding is removed
8be66a
+                                                             * at front and back (accept in the middle, since
8be66a
+                                                             * that's apparently a thing on Windows). Note
8be66a
+                                                             * that this also blocks usernames consisting of
8be66a
+                                                             * whitespace only. */
8be66a
+                        return false;
8be66a
+
8be66a
+                if (!utf8_is_valid(u)) /* We want to synthesize JSON from this, hence insist on UTF-8 */
8be66a
+                        return false;
8be66a
+
8be66a
+                if (string_has_cc(u, NULL)) /* CC characters are just dangerous (and \n in particular is the
8be66a
+                                             * record separator in /etc/passwd), so we can't allow that. */
8be66a
+                        return false;
8be66a
+
8be66a
+                if (strpbrk(u, ":/")) /* Colons are the field separator in /etc/passwd, we can't allow
8be66a
+                                       * that. Slashes are special to file systems paths and user names
8be66a
+                                       * typically show up in the file system as home directories, hence
8be66a
+                                       * don't allow slashes. */
8be66a
+                        return false;
8be66a
+
8be66a
+                if (in_charset(u, "0123456789")) /* Don't allow fully numeric strings, they might be confused
8be66a
+                                                  * with with UIDs (note that this test is more broad than
8be66a
+                                                  * the parse_uid() test above, as it will cover more than
8be66a
+                                                  * the 32bit range, and it will detect 65535 (which is in
8be66a
+                                                  * invalid UID, even though in the unsigned 32 bit range) */
8be66a
+                        return false;
8be66a
+
8be66a
+                if (u[0] == '-' && in_charset(u + 1, "0123456789")) /* Don't allow negative fully numeric
8be66a
+                                                                     * strings either. After all some people
8be66a
+                                                                     * write 65535 as -1 (even though that's
8be66a
+                                                                     * not even true on 32bit uid_t
8be66a
+                                                                     * anyway) */
8be66a
+                        return false;
8be66a
+
8be66a
+                if (dot_or_dot_dot(u)) /* User names typically become home directory names, and these two are
8be66a
+                                        * special in that context, don't allow that. */
8be66a
+                        return false;
8be66a
+
8be66a
+                /* Compare with strict result and warn if result doesn't match */
8be66a
+                if (FLAGS_SET(flags, VALID_USER_WARN) && !valid_user_group_name(u, 0))
8be66a
+                        log_struct(LOG_NOTICE,
8be66a
+                                   "MESSAGE=Accepting user/group name '%s', which does not match strict user/group name rules.", u,
8be66a
+                                   "USER_GROUP_NAME=%s", u,
8be66a
+                                   "MESSAGE_ID=" SD_MESSAGE_UNSAFE_USER_NAME_STR);
8be66a
+
8be66a
+                /* Note that we make no restrictions on the length in relaxed mode! */
8be66a
+        } else {
8be66a
+                long sz;
8be66a
+                size_t l;
8be66a
+
8be66a
+                /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.437. We are a bit stricter here
8be66a
+                 * however. Specifically we deviate from POSIX rules:
8be66a
+                 *
8be66a
+                 * - We don't allow empty user names (see above)
8be66a
+                 * - We require that names fit into the appropriate utmp field
8be66a
+                 * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator)
8be66a
+                 * - We don't allow dashes or digit as the first character
8be66a
+                 *
8be66a
+                 * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
8be66a
+                 */
8be66a
+
8be66a
+                if (!(u[0] >= 'a' && u[0] <= 'z') &&
8be66a
+                    !(u[0] >= 'A' && u[0] <= 'Z') &&
8be66a
+                    u[0] != '_')
8be66a
+                        return false;
8be66a
+
8be66a
+                for (i = u+1; *i; i++)
8be66a
+                        if (!(*i >= 'a' && *i <= 'z') &&
8be66a
+                            !(*i >= 'A' && *i <= 'Z') &&
8be66a
+                            !(*i >= '0' && *i <= '9') &&
8be66a
+                            !IN_SET(*i, '_', '-'))
8be66a
+                                return false;
8be66a
+
8be66a
+                l = i - u;
8be66a
+
8be66a
+                sz = sysconf(_SC_LOGIN_NAME_MAX);
8be66a
+                assert_se(sz > 0);
8be66a
+
8be66a
+                if (l > (size_t) sz)
8be66a
+                        return false;
8be66a
+                if (l > FILENAME_MAX)
8be66a
+                        return false;
8be66a
+                if (l > UT_NAMESIZE - 1)
8be66a
+                        return false;
8be66a
         }
8be66a
 
8be66a
-        if (only_digits_seen)
8be66a
-                return false;
8be66a
-
8be66a
-        sz = sysconf(_SC_LOGIN_NAME_MAX);
8be66a
-        assert_se(sz > 0);
8be66a
-
8be66a
-        if ((size_t) (i-u) > (size_t) sz)
8be66a
-                return false;
8be66a
-
8be66a
-        if ((size_t) (i-u) > UT_NAMESIZE - 1)
8be66a
-                return false;
8be66a
-
8be66a
         return true;
8be66a
 }
8be66a
 
8be66a
-bool valid_user_group_name_or_id_full(const char *u, bool strict) {
8be66a
-
8be66a
-        /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the
8be66a
-         * right range, and not the invalid user ids. */
8be66a
-
8be66a
-        if (isempty(u))
8be66a
-                return false;
8be66a
-
8be66a
-        if (parse_uid(u, NULL) >= 0)
8be66a
-                return true;
8be66a
-
8be66a
-        return valid_user_group_name_full(u, strict);
8be66a
-}
8be66a
-
8be66a
 bool valid_gecos(const char *d) {
8be66a
 
8be66a
         if (!d)
8be66a
diff --git a/src/basic/user-util.h b/src/basic/user-util.h
8be66a
index 5ad0b2a2f9..939bded40d 100644
8be66a
--- a/src/basic/user-util.h
8be66a
+++ b/src/basic/user-util.h
8be66a
@@ -78,20 +78,13 @@ static inline bool userns_supported(void) {
8be66a
         return access("/proc/self/uid_map", F_OK) >= 0;
8be66a
 }
8be66a
 
8be66a
-bool valid_user_group_name_full(const char *u, bool strict);
8be66a
-bool valid_user_group_name_or_id_full(const char *u, bool strict);
8be66a
-static inline bool valid_user_group_name(const char *u) {
8be66a
-        return valid_user_group_name_full(u, true);
8be66a
-}
8be66a
-static inline bool valid_user_group_name_or_id(const char *u) {
8be66a
-        return valid_user_group_name_or_id_full(u, true);
8be66a
-}
8be66a
-static inline bool valid_user_group_name_compat(const char *u) {
8be66a
-        return valid_user_group_name_full(u, false);
8be66a
-}
8be66a
-static inline bool valid_user_group_name_or_id_compat(const char *u) {
8be66a
-        return valid_user_group_name_or_id_full(u, false);
8be66a
-}
8be66a
+typedef enum ValidUserFlags {
8be66a
+        VALID_USER_RELAX         = 1 << 0,
8be66a
+        VALID_USER_WARN          = 1 << 1,
8be66a
+        VALID_USER_ALLOW_NUMERIC = 1 << 2,
8be66a
+} ValidUserFlags;
8be66a
+
8be66a
+bool valid_user_group_name(const char *u, ValidUserFlags flags);
8be66a
 bool valid_gecos(const char *d);
8be66a
 bool valid_home(const char *p);
8be66a
 
8be66a
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
8be66a
index e004fb55c9..8348663000 100644
8be66a
--- a/src/core/dbus-execute.c
8be66a
+++ b/src/core/dbus-execute.c
8be66a
@@ -1113,10 +1113,10 @@ int bus_exec_context_set_transient_property(
8be66a
         flags |= UNIT_PRIVATE;
8be66a
 
8be66a
         if (streq(name, "User"))
8be66a
-                return bus_set_transient_user_compat(u, name, &c->user, message, flags, error);
8be66a
+                return bus_set_transient_user_relaxed(u, name, &c->user, message, flags, error);
8be66a
 
8be66a
         if (streq(name, "Group"))
8be66a
-                return bus_set_transient_user_compat(u, name, &c->group, message, flags, error);
8be66a
+                return bus_set_transient_user_relaxed(u, name, &c->group, message, flags, error);
8be66a
 
8be66a
         if (streq(name, "TTYPath"))
8be66a
                 return bus_set_transient_path(u, name, &c->tty_path, message, flags, error);
8be66a
@@ -1298,7 +1298,7 @@ int bus_exec_context_set_transient_property(
8be66a
                         return r;
8be66a
 
8be66a
                 STRV_FOREACH(p, l)
8be66a
-                        if (!isempty(*p) && !valid_user_group_name_or_id_compat(*p))
8be66a
+                        if (!isempty(*p) && !valid_user_group_name(*p, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN))
8be66a
                                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
8be66a
                                                          "Invalid supplementary group names");
8be66a
 
8be66a
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
8be66a
index 0a1d3df42f..7488f22116 100644
8be66a
--- a/src/core/dbus-manager.c
8be66a
+++ b/src/core/dbus-manager.c
8be66a
@@ -1762,7 +1762,7 @@ static int method_lookup_dynamic_user_by_name(sd_bus_message *message, void *use
8be66a
 
8be66a
         if (!MANAGER_IS_SYSTEM(m))
8be66a
                 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance.");
8be66a
-        if (!valid_user_group_name(name))
8be66a
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
8be66a
                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name invalid: %s", name);
8be66a
 
8be66a
         r = dynamic_user_lookup_name(m, name, &uid);
8be66a
diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c
8be66a
index 8fdbc05409..fa6bbe2c6f 100644
8be66a
--- a/src/core/dbus-socket.c
8be66a
+++ b/src/core/dbus-socket.c
8be66a
@@ -281,10 +281,10 @@ static int bus_socket_set_transient_property(
8be66a
                 return bus_set_transient_fdname(u, name, &s->fdname, message, flags, error);
8be66a
 
8be66a
         if (streq(name, "SocketUser"))
8be66a
-                return bus_set_transient_user_compat(u, name, &s->user, message, flags, error);
8be66a
+                return bus_set_transient_user_relaxed(u, name, &s->user, message, flags, error);
8be66a
 
8be66a
         if (streq(name, "SocketGroup"))
8be66a
-                return bus_set_transient_user_compat(u, name, &s->group, message, flags, error);
8be66a
+                return bus_set_transient_user_relaxed(u, name, &s->group, message, flags, error);
8be66a
 
8be66a
         if (streq(name, "BindIPv6Only"))
8be66a
                 return bus_set_transient_bind_ipv6_only(u, name, &s->bind_ipv6_only, message, flags, error);
8be66a
diff --git a/src/core/dbus-util.c b/src/core/dbus-util.c
8be66a
index 7862beaacb..951450e53d 100644
8be66a
--- a/src/core/dbus-util.c
8be66a
+++ b/src/core/dbus-util.c
8be66a
@@ -30,7 +30,12 @@ int bus_property_get_triggered_unit(
8be66a
 
8be66a
 BUS_DEFINE_SET_TRANSIENT(mode_t, "u", uint32_t, mode_t, "%040o");
8be66a
 BUS_DEFINE_SET_TRANSIENT(unsigned, "u", uint32_t, unsigned, "%" PRIu32);
8be66a
-BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(user_compat, valid_user_group_name_or_id_compat);
8be66a
+
8be66a
+static inline bool valid_user_group_name_or_id_relaxed(const char *u) {
8be66a
+        return valid_user_group_name(u, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX);
8be66a
+}
8be66a
+
8be66a
+BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(user_relaxed, valid_user_group_name_or_id_relaxed);
8be66a
 BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(path, path_is_absolute);
8be66a
 
8be66a
 int bus_set_transient_string(
8be66a
diff --git a/src/core/dbus-util.h b/src/core/dbus-util.h
8be66a
index a3316c6701..713b464dd9 100644
8be66a
--- a/src/core/dbus-util.h
8be66a
+++ b/src/core/dbus-util.h
8be66a
@@ -235,7 +235,7 @@ int bus_property_get_triggered_unit(sd_bus *bus, const char *path, const char *i
8be66a
 
8be66a
 int bus_set_transient_mode_t(Unit *u, const char *name, mode_t *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
8be66a
 int bus_set_transient_unsigned(Unit *u, const char *name, unsigned *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
8be66a
-int bus_set_transient_user_compat(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
8be66a
+int bus_set_transient_user_relaxed(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
8be66a
 int bus_set_transient_path(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
8be66a
 int bus_set_transient_string(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
8be66a
 int bus_set_transient_bool(Unit *u, const char *name, bool *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
8be66a
diff --git a/src/core/dynamic-user.c b/src/core/dynamic-user.c
8be66a
index 021fd93a76..548b3cc9df 100644
8be66a
--- a/src/core/dynamic-user.c
8be66a
+++ b/src/core/dynamic-user.c
8be66a
@@ -108,7 +108,7 @@ static int dynamic_user_acquire(Manager *m, const char *name, DynamicUser** ret)
8be66a
                 return 0;
8be66a
         }
8be66a
 
8be66a
-        if (!valid_user_group_name_or_id(name))
8be66a
+        if (!valid_user_group_name(name, VALID_USER_ALLOW_NUMERIC))
8be66a
                 return -EINVAL;
8be66a
 
8be66a
         if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, storage_socket) < 0)
8be66a
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
8be66a
index ba81d94504..e0d7b8f7f8 100644
8be66a
--- a/src/core/load-fragment.c
8be66a
+++ b/src/core/load-fragment.c
8be66a
@@ -1932,7 +1932,7 @@ int config_parse_user_group_compat(
8be66a
                 return -ENOEXEC;
8be66a
         }
8be66a
 
8be66a
-        if (!valid_user_group_name_or_id_compat(k)) {
8be66a
+        if (!valid_user_group_name(k, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN)) {
8be66a
                 log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k);
8be66a
                 return -ENOEXEC;
8be66a
         }
8be66a
@@ -1986,7 +1986,7 @@ int config_parse_user_group_strv_compat(
8be66a
                         return -ENOEXEC;
8be66a
                 }
8be66a
 
8be66a
-                if (!valid_user_group_name_or_id_compat(k)) {
8be66a
+                if (!valid_user_group_name(k, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN)) {
8be66a
                         log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k);
8be66a
                         return -ENOEXEC;
8be66a
                 }
8be66a
diff --git a/src/core/unit.c b/src/core/unit.c
8be66a
index ffbf3cfd48..cd3e7c806d 100644
8be66a
--- a/src/core/unit.c
8be66a
+++ b/src/core/unit.c
8be66a
@@ -4088,7 +4088,7 @@ static int user_from_unit_name(Unit *u, char **ret) {
8be66a
         if (r < 0)
8be66a
                 return r;
8be66a
 
8be66a
-        if (valid_user_group_name(n)) {
8be66a
+        if (valid_user_group_name(n, 0)) {
8be66a
                 *ret = TAKE_PTR(n);
8be66a
                 return 0;
8be66a
         }
8be66a
diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c
8be66a
index f8db27ae27..615c710257 100644
8be66a
--- a/src/nss-systemd/nss-systemd.c
8be66a
+++ b/src/nss-systemd/nss-systemd.c
8be66a
@@ -123,7 +123,7 @@ static int direct_lookup_uid(uid_t uid, char **ret) {
8be66a
         r = readlink_malloc(path, &s);
8be66a
         if (r < 0)
8be66a
                 return r;
8be66a
-        if (!valid_user_group_name(s)) { /* extra safety check */
8be66a
+        if (!valid_user_group_name(s, VALID_USER_RELAX)) { /* extra safety check */
8be66a
                 free(s);
8be66a
                 return -EINVAL;
8be66a
         }
8be66a
@@ -153,7 +153,7 @@ enum nss_status _nss_systemd_getpwnam_r(
8be66a
 
8be66a
         /* If the username is not valid, then we don't know it. Ideally libc would filter these for us anyway. We don't
8be66a
          * generate EINVAL here, because it isn't really out business to complain about invalid user names. */
8be66a
-        if (!valid_user_group_name(name))
8be66a
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
8be66a
                 return NSS_STATUS_NOTFOUND;
8be66a
 
8be66a
         /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
8be66a
@@ -356,7 +356,7 @@ enum nss_status _nss_systemd_getgrnam_r(
8be66a
         assert(name);
8be66a
         assert(gr);
8be66a
 
8be66a
-        if (!valid_user_group_name(name))
8be66a
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
8be66a
                 return NSS_STATUS_NOTFOUND;
8be66a
 
8be66a
         /* Synthesize records for root and nobody, in case they are missing form /etc/group */
8be66a
diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h
8be66a
index bdd4fd3974..847b698ba4 100644
8be66a
--- a/src/systemd/sd-messages.h
8be66a
+++ b/src/systemd/sd-messages.h
8be66a
@@ -152,6 +152,9 @@ _SD_BEGIN_DECLARATIONS;
8be66a
 #define SD_MESSAGE_DNSSEC_DOWNGRADE       SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57)
8be66a
 #define SD_MESSAGE_DNSSEC_DOWNGRADE_STR   SD_ID128_MAKE_STR(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57)
8be66a
 
8be66a
+#define SD_MESSAGE_UNSAFE_USER_NAME       SD_ID128_MAKE(b6,1f,da,c6,12,e9,4b,91,82,28,5b,99,88,43,06,1f)
8be66a
+#define SD_MESSAGE_UNSAFE_USER_NAME_STR   SD_ID128_MAKE_STR(b6,1f,da,c6,12,e9,4b,91,82,28,5b,99,88,43,06,1f)
8be66a
+
8be66a
 _SD_END_DECLARATIONS;
8be66a
 
8be66a
 #endif
8be66a
diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c
8be66a
index 33959d3c11..a374ebaaf4 100644
8be66a
--- a/src/sysusers/sysusers.c
8be66a
+++ b/src/sysusers/sysusers.c
8be66a
@@ -1413,7 +1413,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
8be66a
                         return r;
8be66a
                 }
8be66a
 
8be66a
-                if (!valid_user_group_name(resolved_name)) {
8be66a
+                if (!valid_user_group_name(resolved_name, 0)) {
8be66a
                         log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name);
8be66a
                         return -EINVAL;
8be66a
                 }
8be66a
@@ -1524,7 +1524,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
8be66a
                         return -EINVAL;
8be66a
                 }
8be66a
 
8be66a
-                if (!valid_user_group_name(resolved_id)) {
8be66a
+                if (!valid_user_group_name(resolved_id, 0)) {
8be66a
                         log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id);
8be66a
                         return -EINVAL;
8be66a
                 }
8be66a
diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c
8be66a
index 56079f1486..31ac018da9 100644
8be66a
--- a/src/test/test-user-util.c
8be66a
+++ b/src/test/test-user-util.c
8be66a
@@ -131,144 +131,163 @@ static void test_uid_ptr(void) {
8be66a
         assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000);
8be66a
 }
8be66a
 
8be66a
-static void test_valid_user_group_name_compat(void) {
8be66a
+static void test_valid_user_group_name_relaxed(void) {
8be66a
         log_info("/* %s */", __func__);
8be66a
 
8be66a
-        assert_se(!valid_user_group_name_compat(NULL));
8be66a
-        assert_se(!valid_user_group_name_compat(""));
8be66a
-        assert_se(!valid_user_group_name_compat("1"));
8be66a
-        assert_se(!valid_user_group_name_compat("65535"));
8be66a
-        assert_se(!valid_user_group_name_compat("-1"));
8be66a
-        assert_se(!valid_user_group_name_compat("-kkk"));
8be66a
-        assert_se(!valid_user_group_name_compat("rööt"));
8be66a
-        assert_se(!valid_user_group_name_compat("."));
8be66a
-        assert_se(!valid_user_group_name_compat(".eff"));
8be66a
-        assert_se(!valid_user_group_name_compat("foo\nbar"));
8be66a
-        assert_se(!valid_user_group_name_compat("0123456789012345678901234567890123456789"));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat("aaa:bbb"));
8be66a
-        assert_se(!valid_user_group_name_compat("."));
8be66a
-        assert_se(!valid_user_group_name_compat(".1"));
8be66a
-        assert_se(!valid_user_group_name_compat(".65535"));
8be66a
-        assert_se(!valid_user_group_name_compat(".-1"));
8be66a
-        assert_se(!valid_user_group_name_compat(".-kkk"));
8be66a
-        assert_se(!valid_user_group_name_compat(".rööt"));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat(".aaa:bbb"));
8be66a
-
8be66a
-        assert_se(valid_user_group_name_compat("root"));
8be66a
-        assert_se(valid_user_group_name_compat("lennart"));
8be66a
-        assert_se(valid_user_group_name_compat("LENNART"));
8be66a
-        assert_se(valid_user_group_name_compat("_kkk"));
8be66a
-        assert_se(valid_user_group_name_compat("kkk-"));
8be66a
-        assert_se(valid_user_group_name_compat("kk-k"));
8be66a
-        assert_se(valid_user_group_name_compat("eff.eff"));
8be66a
-        assert_se(valid_user_group_name_compat("eff."));
8be66a
-
8be66a
-        assert_se(valid_user_group_name_compat("some5"));
8be66a
-        assert_se(valid_user_group_name_compat("5some"));
8be66a
-        assert_se(valid_user_group_name_compat("INNER5NUMBER"));
8be66a
+        assert_se(!valid_user_group_name(NULL, VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("", VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("1", VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("65535", VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("-1", VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("foo\nbar", VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_RELAX|VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name(".aaa:bbb", VALID_USER_RELAX|VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name(".", VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("..", VALID_USER_RELAX));
8be66a
+
8be66a
+        assert_se(valid_user_group_name("root", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("lennart", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("LENNART", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("_kkk", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("kkk-", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("kk-k", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("eff.eff", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("eff.", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("-kkk", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("rööt", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name(".eff", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name(".1", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name(".65535", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name(".-1", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name(".-kkk", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name(".rööt", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("...", VALID_USER_RELAX));
8be66a
+
8be66a
+        assert_se(valid_user_group_name("some5", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("5some", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_RELAX));
8be66a
+
8be66a
+        assert_se(valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("Dāvis", VALID_USER_RELAX));
8be66a
 }
8be66a
 
8be66a
 static void test_valid_user_group_name(void) {
8be66a
         log_info("/* %s */", __func__);
8be66a
 
8be66a
-        assert_se(!valid_user_group_name(NULL));
8be66a
-        assert_se(!valid_user_group_name(""));
8be66a
-        assert_se(!valid_user_group_name("1"));
8be66a
-        assert_se(!valid_user_group_name("65535"));
8be66a
-        assert_se(!valid_user_group_name("-1"));
8be66a
-        assert_se(!valid_user_group_name("-kkk"));
8be66a
-        assert_se(!valid_user_group_name("rööt"));
8be66a
-        assert_se(!valid_user_group_name("."));
8be66a
-        assert_se(!valid_user_group_name(".eff"));
8be66a
-        assert_se(!valid_user_group_name("foo\nbar"));
8be66a
-        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789"));
8be66a
-        assert_se(!valid_user_group_name_or_id("aaa:bbb"));
8be66a
-        assert_se(!valid_user_group_name("."));
8be66a
-        assert_se(!valid_user_group_name(".1"));
8be66a
-        assert_se(!valid_user_group_name(".65535"));
8be66a
-        assert_se(!valid_user_group_name(".-1"));
8be66a
-        assert_se(!valid_user_group_name(".-kkk"));
8be66a
-        assert_se(!valid_user_group_name(".rööt"));
8be66a
-        assert_se(!valid_user_group_name_or_id(".aaa:bbb"));
8be66a
-
8be66a
-        assert_se(valid_user_group_name("root"));
8be66a
-        assert_se(valid_user_group_name("lennart"));
8be66a
-        assert_se(valid_user_group_name("LENNART"));
8be66a
-        assert_se(valid_user_group_name("_kkk"));
8be66a
-        assert_se(valid_user_group_name("kkk-"));
8be66a
-        assert_se(valid_user_group_name("kk-k"));
8be66a
-        assert_se(!valid_user_group_name("eff.eff"));
8be66a
-        assert_se(!valid_user_group_name("eff."));
8be66a
-
8be66a
-        assert_se(valid_user_group_name("some5"));
8be66a
-        assert_se(!valid_user_group_name("5some"));
8be66a
-        assert_se(valid_user_group_name("INNER5NUMBER"));
8be66a
+        assert_se(!valid_user_group_name(NULL, 0));
8be66a
+        assert_se(!valid_user_group_name("", 0));
8be66a
+        assert_se(!valid_user_group_name("1", 0));
8be66a
+        assert_se(!valid_user_group_name("65535", 0));
8be66a
+        assert_se(!valid_user_group_name("-1", 0));
8be66a
+        assert_se(!valid_user_group_name("-kkk", 0));
8be66a
+        assert_se(!valid_user_group_name("rööt", 0));
8be66a
+        assert_se(!valid_user_group_name(".", 0));
8be66a
+        assert_se(!valid_user_group_name(".eff", 0));
8be66a
+        assert_se(!valid_user_group_name("foo\nbar", 0));
8be66a
+        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", 0));
8be66a
+        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name(".", 0));
8be66a
+        assert_se(!valid_user_group_name("..", 0));
8be66a
+        assert_se(!valid_user_group_name("...", 0));
8be66a
+        assert_se(!valid_user_group_name(".1", 0));
8be66a
+        assert_se(!valid_user_group_name(".65535", 0));
8be66a
+        assert_se(!valid_user_group_name(".-1", 0));
8be66a
+        assert_se(!valid_user_group_name(".-kkk", 0));
8be66a
+        assert_se(!valid_user_group_name(".rööt", 0));
8be66a
+        assert_se(!valid_user_group_name(".aaa:bbb", VALID_USER_ALLOW_NUMERIC));
8be66a
+
8be66a
+        assert_se(valid_user_group_name("root", 0));
8be66a
+        assert_se(valid_user_group_name("lennart", 0));
8be66a
+        assert_se(valid_user_group_name("LENNART", 0));
8be66a
+        assert_se(valid_user_group_name("_kkk", 0));
8be66a
+        assert_se(valid_user_group_name("kkk-", 0));
8be66a
+        assert_se(valid_user_group_name("kk-k", 0));
8be66a
+        assert_se(!valid_user_group_name("eff.eff", 0));
8be66a
+        assert_se(!valid_user_group_name("eff.", 0));
8be66a
+
8be66a
+        assert_se(valid_user_group_name("some5", 0));
8be66a
+        assert_se(!valid_user_group_name("5some", 0));
8be66a
+        assert_se(valid_user_group_name("INNER5NUMBER", 0));
8be66a
+
8be66a
+        assert_se(!valid_user_group_name("piff.paff@ad.domain.example", 0));
8be66a
+        assert_se(!valid_user_group_name("Dāvis", 0));
8be66a
 }
8be66a
 
8be66a
-static void test_valid_user_group_name_or_id_compat(void) {
8be66a
+static void test_valid_user_group_name_or_numeric_relaxed(void) {
8be66a
         log_info("/* %s */", __func__);
8be66a
 
8be66a
-        assert_se(!valid_user_group_name_or_id_compat(NULL));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat(""));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("0"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("1"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("65534"));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat("65535"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("65536"));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat("-1"));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat("-kkk"));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat("rööt"));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat("."));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat(".eff"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("eff.eff"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("eff."));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat("foo\nbar"));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat("0123456789012345678901234567890123456789"));
8be66a
-        assert_se(!valid_user_group_name_or_id_compat("aaa:bbb"));
8be66a
-
8be66a
-        assert_se(valid_user_group_name_or_id_compat("root"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("lennart"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("LENNART"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("_kkk"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("kkk-"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("kk-k"));
8be66a
-
8be66a
-        assert_se(valid_user_group_name_or_id_compat("some5"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("5some"));
8be66a
-        assert_se(valid_user_group_name_or_id_compat("INNER5NUMBER"));
8be66a
+        assert_se(!valid_user_group_name(NULL, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("0", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("1", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("65534", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("65535", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("65536", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("-1", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("foo\nbar", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name(".", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(!valid_user_group_name("..", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+
8be66a
+        assert_se(valid_user_group_name("root", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("lennart", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("LENNART", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("_kkk", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("kkk-", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("kk-k", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("-kkk", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("rööt", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name(".eff", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("eff.eff", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("eff.", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("...", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+
8be66a
+        assert_se(valid_user_group_name("some5", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("5some", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+
8be66a
+        assert_se(valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
+        assert_se(valid_user_group_name("Dāvis", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
8be66a
 }
8be66a
 
8be66a
-static void test_valid_user_group_name_or_id(void) {
8be66a
+static void test_valid_user_group_name_or_numeric(void) {
8be66a
         log_info("/* %s */", __func__);
8be66a
 
8be66a
-        assert_se(!valid_user_group_name_or_id(NULL));
8be66a
-        assert_se(!valid_user_group_name_or_id(""));
8be66a
-        assert_se(valid_user_group_name_or_id("0"));
8be66a
-        assert_se(valid_user_group_name_or_id("1"));
8be66a
-        assert_se(valid_user_group_name_or_id("65534"));
8be66a
-        assert_se(!valid_user_group_name_or_id("65535"));
8be66a
-        assert_se(valid_user_group_name_or_id("65536"));
8be66a
-        assert_se(!valid_user_group_name_or_id("-1"));
8be66a
-        assert_se(!valid_user_group_name_or_id("-kkk"));
8be66a
-        assert_se(!valid_user_group_name_or_id("rööt"));
8be66a
-        assert_se(!valid_user_group_name_or_id("."));
8be66a
-        assert_se(!valid_user_group_name_or_id(".eff"));
8be66a
-        assert_se(!valid_user_group_name_or_id("eff.eff"));
8be66a
-        assert_se(!valid_user_group_name_or_id("eff."));
8be66a
-        assert_se(!valid_user_group_name_or_id("foo\nbar"));
8be66a
-        assert_se(!valid_user_group_name_or_id("0123456789012345678901234567890123456789"));
8be66a
-        assert_se(!valid_user_group_name_or_id("aaa:bbb"));
8be66a
-
8be66a
-        assert_se(valid_user_group_name_or_id("root"));
8be66a
-        assert_se(valid_user_group_name_or_id("lennart"));
8be66a
-        assert_se(valid_user_group_name_or_id("LENNART"));
8be66a
-        assert_se(valid_user_group_name_or_id("_kkk"));
8be66a
-        assert_se(valid_user_group_name_or_id("kkk-"));
8be66a
-        assert_se(valid_user_group_name_or_id("kk-k"));
8be66a
-
8be66a
-        assert_se(valid_user_group_name_or_id("some5"));
8be66a
-        assert_se(!valid_user_group_name_or_id("5some"));
8be66a
-        assert_se(valid_user_group_name_or_id("INNER5NUMBER"));
8be66a
+        assert_se(!valid_user_group_name(NULL, VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(valid_user_group_name("0", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(valid_user_group_name("1", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(valid_user_group_name("65534", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("65535", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(valid_user_group_name("65536", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("-1", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("-kkk", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("rööt", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name(".", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("..", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("...", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name(".eff", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("eff.eff", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("eff.", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("foo\nbar", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC));
8be66a
+
8be66a
+        assert_se(valid_user_group_name("root", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(valid_user_group_name("lennart", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(valid_user_group_name("LENNART", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(valid_user_group_name("_kkk", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(valid_user_group_name("kkk-", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(valid_user_group_name("kk-k", VALID_USER_ALLOW_NUMERIC));
8be66a
+
8be66a
+        assert_se(valid_user_group_name("some5", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("5some", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_ALLOW_NUMERIC));
8be66a
+
8be66a
+        assert_se(!valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_ALLOW_NUMERIC));
8be66a
+        assert_se(!valid_user_group_name("Dāvis", VALID_USER_ALLOW_NUMERIC));
8be66a
 }
8be66a
 
8be66a
 static void test_valid_gecos(void) {
8be66a
@@ -367,10 +386,10 @@ int main(int argc, char*argv[]) {
8be66a
         test_parse_uid();
8be66a
         test_uid_ptr();
8be66a
 
8be66a
-        test_valid_user_group_name_compat();
8be66a
+        test_valid_user_group_name_relaxed();
8be66a
         test_valid_user_group_name();
8be66a
-        test_valid_user_group_name_or_id_compat();
8be66a
-        test_valid_user_group_name_or_id();
8be66a
+        test_valid_user_group_name_or_numeric_relaxed();
8be66a
+        test_valid_user_group_name_or_numeric();
8be66a
         test_valid_gecos();
8be66a
         test_valid_home();
8be66a