]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
user-util: rework how we validate user names
authorLennart Poettering <lennart@poettering.net>
Sat, 4 Apr 2020 10:23:02 +0000 (12:23 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 8 Apr 2020 15:11:20 +0000 (17:11 +0200)
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
23 files changed:
src/basic/user-util.c
src/basic/user-util.h
src/core/dbus-execute.c
src/core/dbus-manager.c
src/core/dbus-socket.c
src/core/dbus-util.c
src/core/dbus-util.h
src/core/dynamic-user.c
src/core/load-fragment.c
src/core/unit.c
src/home/home-util.c
src/home/homectl.c
src/home/homed-manager-bus.c
src/home/pam_systemd_home.c
src/nss-systemd/nss-systemd.c
src/shared/group-record.c
src/shared/json.c
src/shared/json.h
src/shared/user-record.c
src/shared/userdb.c
src/systemd/sd-messages.h
src/sysusers/sysusers.c
src/test/test-user-util.c

index 1510fc96ef955e9c47f274e4845743b653b13287..2e3580017d2ece6f6f5fa729bda02567fa27ed81 100644 (file)
@@ -10,6 +10,8 @@
 #include <unistd.h>
 #include <utmp.h>
 
+#include "sd-messages.h"
+
 #include "alloc-util.h"
 #include "errno-util.h"
 #include "fd-util.h"
@@ -18,6 +20,7 @@
 #include "macro.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "path-util.h"
 #include "random-util.h"
 #include "string-util.h"
 #include "strv.h"
@@ -698,90 +701,123 @@ int take_etc_passwd_lock(const char *root) {
         return fd;
 }
 
-bool valid_user_group_name_full(const char *u, bool strict) {
+bool valid_user_group_name(const char *u, ValidUserFlags flags) {
         const char *i;
-        long sz;
-        bool warned = false;
 
-        /* Checks if the specified name is a valid user/group name. Also see POSIX IEEE Std 1003.1-2008, 2016 Edition,
-         * 3.437. We are a bit stricter here however. Specifically we deviate from POSIX rules:
-         *
-         * - We require that names fit into the appropriate utmp field
-         * - We don't allow empty user names
-         * - No dots in the first character
-         *
-         * If strict==true, additionally:
-         * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator)
-         * - We don't allow a digit as the first character
+        /* Checks if the specified name is a valid user/group name. There are two flavours of this call:
+         * strict mode is the default which is POSIX plus some extra rules; and relaxed mode where we accept
+         * pretty much everything except the really worst offending names.
          *
-         * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
-         */
+         * Whenever we synthesize users ourselves we should use the strict mode. But when we process users
+         * created by other stuff, let's be more liberal. */
 
-        if (isempty(u))
+        if (isempty(u)) /* An empty user name is never valid */
                 return false;
 
-        if (!(u[0] >= 'a' && u[0] <= 'z') &&
-            !(u[0] >= 'A' && u[0] <= 'Z') &&
-            !(u[0] >= '0' && u[0] <= '9' && !strict) &&
-            u[0] != '_')
-                return false;
-
-        bool only_digits_seen = u[0] >= '0' && u[0] <= '9';
+        if (parse_uid(u, NULL) >= 0) /* Something that parses as numeric UID string is valid exactly when the
+                                      * flag for it is set */
+                return FLAGS_SET(flags, VALID_USER_ALLOW_NUMERIC);
+
+        if (FLAGS_SET(flags, VALID_USER_RELAX)) {
+
+                /* In relaxed mode we just check very superficially. Apparently SSSD and other stuff is
+                 * extremely liberal (way too liberal if you ask me, even inserting "@" in user names, which
+                 * is bound to cause problems for example when used with an MTA), hence only filter the most
+                 * obvious cases, or where things would result in an invalid entry if such a user name would
+                 * show up in /etc/passwd (or equivalent getent output).
+                 *
+                 * Note that we stepped far out of POSIX territory here. It's not our fault though, but
+                 * SSSD's, Samba's and everybody else who ignored POSIX on this. (I mean, I am happy to step
+                 * outside of POSIX' bounds any day, but I must say in this case I probably wouldn't
+                 * have...) */
+
+                if (startswith(u, " ") || endswith(u, " ")) /* At least expect whitespace padding is removed
+                                                             * at front and back (accept in the middle, since
+                                                             * that's apparently a thing on Windows). Note
+                                                             * that this also blocks usernames consisting of
+                                                             * whitespace only. */
+                        return false;
 
-        if (only_digits_seen) {
-                log_warning("User or group name \"%s\" starts with a digit, accepting for compatibility.", u);
-                warned = true;
-        }
+                if (!utf8_is_valid(u)) /* We want to synthesize JSON from this, hence insist on UTF-8 */
+                        return false;
 
-        for (i = u+1; *i; i++) {
-                if (((*i >= 'a' && *i <= 'z') ||
-                     (*i >= 'A' && *i <= 'Z') ||
-                     (*i >= '0' && *i <= '9') ||
-                     IN_SET(*i, '_', '-'))) {
-                        if (!(*i >= '0' && *i <= '9'))
-                                only_digits_seen = false;
-                        continue;
-                        }
-
-                if (*i == '.' && !strict) {
-                        if (!warned) {
-                                log_warning("Bad user or group name \"%s\", accepting for compatibility.", u);
-                                warned = true;
-                        }
-
-                        continue;
-                }
+                if (string_has_cc(u, NULL)) /* CC characters are just dangerous (and \n in particular is the
+                                             * record separator in /etc/passwd), so we can't allow that. */
+                        return false;
 
-                return false;
-        }
+                if (strpbrk(u, ":/")) /* Colons are the field separator in /etc/passwd, we can't allow
+                                       * that. Slashes are special to file systems paths and user names
+                                       * typically show up in the file system as home directories, hence
+                                       * don't allow slashes. */
+                        return false;
 
-        if (only_digits_seen)
-                return false;
+                if (in_charset(u, "0123456789")) /* Don't allow fully numeric strings, they might be confused
+                                                  * with with UIDs (note that this test is more broad than
+                                                  * the parse_uid() test above, as it will cover more than
+                                                  * the 32bit range, and it will detect 65535 (which is in
+                                                  * invalid UID, even though in the unsigned 32 bit range) */
+                        return false;
 
-        sz = sysconf(_SC_LOGIN_NAME_MAX);
-        assert_se(sz > 0);
+                if (u[0] == '-' && in_charset(u + 1, "0123456789")) /* Don't allow negative fully numeric
+                                                                     * strings either. After all some people
+                                                                     * write 65535 as -1 (even though that's
+                                                                     * not even true on 32bit uid_t
+                                                                     * anyway) */
+                        return false;
 
-        if ((size_t) (i-u) > (size_t) sz)
-                return false;
+                if (dot_or_dot_dot(u)) /* User names typically become home directory names, and these two are
+                                        * special in that context, don't allow that. */
+                        return false;
 
-        if ((size_t) (i-u) > UT_NAMESIZE - 1)
-                return false;
+                /* Compare with strict result and warn if result doesn't match */
+                if (FLAGS_SET(flags, VALID_USER_WARN) && !valid_user_group_name(u, 0))
+                        log_struct(LOG_NOTICE,
+                                   "MESSAGE=Accepting user/group name '%s', which does not match strict user/group name rules.", u,
+                                   "USER_GROUP_NAME=%s", u,
+                                   "MESSAGE_ID=" SD_MESSAGE_UNSAFE_USER_NAME_STR);
 
-        return true;
-}
+                /* Note that we make no restrictions on the length in relaxed mode! */
+        } else {
+                long sz;
+                size_t l;
+
+                /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.437. We are a bit stricter here
+                 * however. Specifically we deviate from POSIX rules:
+                 *
+                 * - We don't allow empty user names (see above)
+                 * - We require that names fit into the appropriate utmp field
+                 * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator)
+                 * - We don't allow dashes or digit as the first character
+                 *
+                 * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
+                 */
+
+                if (!(u[0] >= 'a' && u[0] <= 'z') &&
+                    !(u[0] >= 'A' && u[0] <= 'Z') &&
+                    u[0] != '_')
+                        return false;
 
-bool valid_user_group_name_or_id_full(const char *u, bool strict) {
+                for (i = u+1; *i; i++)
+                        if (!(*i >= 'a' && *i <= 'z') &&
+                            !(*i >= 'A' && *i <= 'Z') &&
+                            !(*i >= '0' && *i <= '9') &&
+                            !IN_SET(*i, '_', '-'))
+                                return false;
 
-        /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the
-         * right range, and not the invalid user ids. */
+                l = i - u;
 
-        if (isempty(u))
-                return false;
+                sz = sysconf(_SC_LOGIN_NAME_MAX);
+                assert_se(sz > 0);
 
-        if (parse_uid(u, NULL) >= 0)
-                return true;
+                if (l > (size_t) sz)
+                        return false;
+                if (l > FILENAME_MAX)
+                        return false;
+                if (l > UT_NAMESIZE - 1)
+                        return false;
+        }
 
-        return valid_user_group_name_full(u, strict);
+        return true;
 }
 
 bool valid_gecos(const char *d) {
index 6796ac42c1df445a5cfb8bfa5b0d7ceeceede242..1f267d21a3f3f188af142b3f7e2925b9626fd450 100644 (file)
@@ -97,20 +97,13 @@ static inline bool userns_supported(void) {
         return access("/proc/self/uid_map", F_OK) >= 0;
 }
 
-bool valid_user_group_name_full(const char *u, bool strict);
-bool valid_user_group_name_or_id_full(const char *u, bool strict);
-static inline bool valid_user_group_name(const char *u) {
-        return valid_user_group_name_full(u, true);
-}
-static inline bool valid_user_group_name_or_id(const char *u) {
-        return valid_user_group_name_or_id_full(u, true);
-}
-static inline bool valid_user_group_name_compat(const char *u) {
-        return valid_user_group_name_full(u, false);
-}
-static inline bool valid_user_group_name_or_id_compat(const char *u) {
-        return valid_user_group_name_or_id_full(u, false);
-}
+typedef enum ValidUserFlags {
+        VALID_USER_RELAX         = 1 << 0,
+        VALID_USER_WARN          = 1 << 1,
+        VALID_USER_ALLOW_NUMERIC = 1 << 2,
+} ValidUserFlags;
+
+bool valid_user_group_name(const char *u, ValidUserFlags flags);
 bool valid_gecos(const char *d);
 bool valid_home(const char *p);
 
index 5696a60ba80bcc33bc8263d76006f7b5007006be..93857436b4efea57c8bedb42f168fbc5fd2e15bd 100644 (file)
@@ -1204,10 +1204,10 @@ int bus_exec_context_set_transient_property(
         flags |= UNIT_PRIVATE;
 
         if (streq(name, "User"))
-                return bus_set_transient_user_compat(u, name, &c->user, message, flags, error);
+                return bus_set_transient_user_relaxed(u, name, &c->user, message, flags, error);
 
         if (streq(name, "Group"))
-                return bus_set_transient_user_compat(u, name, &c->group, message, flags, error);
+                return bus_set_transient_user_relaxed(u, name, &c->group, message, flags, error);
 
         if (streq(name, "TTYPath"))
                 return bus_set_transient_path(u, name, &c->tty_path, message, flags, error);
@@ -1392,7 +1392,7 @@ int bus_exec_context_set_transient_property(
                         return r;
 
                 STRV_FOREACH(p, l)
-                        if (!isempty(*p) && !valid_user_group_name_or_id_compat(*p))
+                        if (!isempty(*p) && !valid_user_group_name(*p, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN))
                                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
                                                          "Invalid supplementary group names");
 
index 60f55aef5fcf53dbf643b092874ab5836d03ac95..ef4bb316ccef73f0f77b79cdb4f89bb1c927c526 100644 (file)
@@ -1643,7 +1643,7 @@ static int method_lookup_dynamic_user_by_name(sd_bus_message *message, void *use
 
         if (!MANAGER_IS_SYSTEM(m))
                 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance.");
-        if (!valid_user_group_name(name))
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name invalid: %s", name);
 
         r = dynamic_user_lookup_name(m, name, &uid);
index c8253c7940d77228fd3f9292185a7af4b4dadc10..ad7b41a95b25ab2a25a1a06ab33fa732e32ff75a 100644 (file)
@@ -278,10 +278,10 @@ static int bus_socket_set_transient_property(
                 return bus_set_transient_fdname(u, name, &s->fdname, message, flags, error);
 
         if (streq(name, "SocketUser"))
-                return bus_set_transient_user_compat(u, name, &s->user, message, flags, error);
+                return bus_set_transient_user_relaxed(u, name, &s->user, message, flags, error);
 
         if (streq(name, "SocketGroup"))
-                return bus_set_transient_user_compat(u, name, &s->group, message, flags, error);
+                return bus_set_transient_user_relaxed(u, name, &s->group, message, flags, error);
 
         if (streq(name, "BindIPv6Only"))
                 return bus_set_transient_bind_ipv6_only(u, name, &s->bind_ipv6_only, message, flags, error);
index 7862beaacb6d164e76b2f3464406d12112307239..951450e53daa418bde5406b806b6fb9e393424df 100644 (file)
@@ -30,7 +30,12 @@ int bus_property_get_triggered_unit(
 
 BUS_DEFINE_SET_TRANSIENT(mode_t, "u", uint32_t, mode_t, "%040o");
 BUS_DEFINE_SET_TRANSIENT(unsigned, "u", uint32_t, unsigned, "%" PRIu32);
-BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(user_compat, valid_user_group_name_or_id_compat);
+
+static inline bool valid_user_group_name_or_id_relaxed(const char *u) {
+        return valid_user_group_name(u, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX);
+}
+
+BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(user_relaxed, valid_user_group_name_or_id_relaxed);
 BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(path, path_is_absolute);
 
 int bus_set_transient_string(
index ec8c245fffea9d461bb21ea2453174432385efe7..654ceb527950600a6adbdfe2bd572909d3b57571 100644 (file)
@@ -236,7 +236,7 @@ int bus_property_get_triggered_unit(sd_bus *bus, const char *path, const char *i
 
 int bus_set_transient_mode_t(Unit *u, const char *name, mode_t *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
 int bus_set_transient_unsigned(Unit *u, const char *name, unsigned *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
-int bus_set_transient_user_compat(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
+int bus_set_transient_user_relaxed(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
 int bus_set_transient_path(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
 int bus_set_transient_string(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
 int bus_set_transient_bool(Unit *u, const char *name, bool *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error);
index f1819b36bc2350ebddb2becad0dd7228d2e0ec20..58b37925bf44c5009baea1077e766d356fe18d55 100644 (file)
@@ -116,7 +116,7 @@ static int dynamic_user_acquire(Manager *m, const char *name, DynamicUser** ret)
                 return 0;
         }
 
-        if (!valid_user_group_name_or_id(name))
+        if (!valid_user_group_name(name, VALID_USER_ALLOW_NUMERIC))
                 return -EINVAL;
 
         if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, storage_socket) < 0)
index 646364eb898788b843cc0e72cdbefd662bb12d33..f3fe73535e3d06195055bc7b17cda563dcd4e6f3 100644 (file)
@@ -2068,7 +2068,7 @@ int config_parse_user_group_compat(
                 return -ENOEXEC;
         }
 
-        if (!valid_user_group_name_or_id_compat(k)) {
+        if (!valid_user_group_name(k, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN)) {
                 log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k);
                 return -ENOEXEC;
         }
@@ -2122,7 +2122,7 @@ int config_parse_user_group_strv_compat(
                         return -ENOEXEC;
                 }
 
-                if (!valid_user_group_name_or_id_compat(k)) {
+                if (!valid_user_group_name(k, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX|VALID_USER_WARN)) {
                         log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID: %s", k);
                         return -ENOEXEC;
                 }
index 96e1a6c3208feb136e49bdfe89a56be28445822a..95d574d8d48d32079ed7b3abb3434ab5b4c47c10 100644 (file)
@@ -4287,7 +4287,7 @@ static int user_from_unit_name(Unit *u, char **ret) {
         if (r < 0)
                 return r;
 
-        if (valid_user_group_name(n)) {
+        if (valid_user_group_name(n, 0)) {
                 *ret = TAKE_PTR(n);
                 return 0;
         }
index a53b0d33913f22d7dbf250fb7214d742d0b9b691..69ab645484d8377ed0629015d73fc5249c6f9c32 100644 (file)
@@ -17,7 +17,7 @@ bool suitable_user_name(const char *name) {
          * restrictive, so that we can change the rules server-side without having to update things
          * client-side too. */
 
-        if (!valid_user_group_name(name))
+        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
index 1ccc053d3feb97699a643b09e563beeeb81ca0b4..66d4bb6bd6be79ad1f19e458fe61e3701042d169 100644 (file)
@@ -540,7 +540,7 @@ static int inspect_home(int argc, char *argv[], void *userdata) {
 
                 r = parse_uid(*i, &uid);
                 if (r < 0) {
-                        if (!valid_user_group_name(*i)) {
+                        if (!valid_user_group_name(*i, 0)) {
                                 log_error("Invalid user name '%s'.", *i);
                                 if (ret == 0)
                                         ret = -EINVAL;
@@ -1395,7 +1395,7 @@ static int create_home(int argc, char *argv[], void *userdata) {
         if (argc >= 2) {
                 /* If a username was specified, use it */
 
-                if (valid_user_group_name(argv[1]))
+                if (valid_user_group_name(argv[1], 0))
                         r = json_variant_set_field_string(&arg_identity_extra, "userName", argv[1]);
                 else {
                         _cleanup_free_ char *un = NULL, *rr = NULL;
@@ -3357,7 +3357,7 @@ static int parse_argv(int argc, char *argv[]) {
                                 if (r == 0)
                                         break;
 
-                                if (!valid_user_group_name(word))
+                                if (!valid_user_group_name(word, 0))
                                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word);
 
                                 mo = json_variant_ref(json_variant_by_key(arg_identity_extra, "memberOf"));
index fce85452743590d886a575ca33ea08008dc07ee0..34a7b4945233223ba0037fab039856053859046c 100644 (file)
@@ -81,7 +81,7 @@ static int method_get_home_by_name(
         r = sd_bus_message_read(message, "s", &user_name);
         if (r < 0)
                 return r;
-        if (!valid_user_group_name(user_name))
+        if (!valid_user_group_name(user_name, 0))
                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
 
         h = hashmap_get(m->homes_by_name, user_name);
@@ -212,7 +212,7 @@ static int method_get_user_record_by_name(
         r = sd_bus_message_read(message, "s", &user_name);
         if (r < 0)
                 return r;
-        if (!valid_user_group_name(user_name))
+        if (!valid_user_group_name(user_name, 0))
                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
 
         h = hashmap_get(m->homes_by_name, user_name);
@@ -287,7 +287,7 @@ static int generic_home_method(
         if (r < 0)
                 return r;
 
-        if (!valid_user_group_name(user_name))
+        if (!valid_user_group_name(user_name, 0))
                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
 
         h = hashmap_get(m->homes_by_name, user_name);
index 440ed85e2c2e0a9accdbe6e89ca0dd1fdd6ceae2..afc72412d41e77e57a622d77bcd9d1d1b56880ad 100644 (file)
@@ -88,7 +88,7 @@ static int acquire_user_record(
 
         /* Let's bypass all IPC complexity for the two user names we know for sure we don't manage, and for
          * user names we don't consider valid. */
-        if (STR_IN_SET(username, "root", NOBODY_USER_NAME) || !valid_user_group_name(username))
+        if (STR_IN_SET(username, "root", NOBODY_USER_NAME) || !valid_user_group_name(username, 0))
                 return PAM_USER_UNKNOWN;
 
         /* Let's check if a previous run determined that this user is not managed by homed. If so, let's exit early */
index 581b7959bd06a27c94288a49c03b2f5dedd3e243..c1e780edcb0e0c3876b4fc899b5825696dbedbf7 100644 (file)
@@ -96,7 +96,7 @@ enum nss_status _nss_systemd_getpwnam_r(
         /* If the username is not valid, then we don't know it. Ideally libc would filter these for us
          * anyway. We don't generate EINVAL here, because it isn't really out business to complain about
          * invalid user names. */
-        if (!valid_user_group_name(name))
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
                 return NSS_STATUS_NOTFOUND;
 
         /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
@@ -193,7 +193,7 @@ enum nss_status _nss_systemd_getgrnam_r(
         assert(gr);
         assert(errnop);
 
-        if (!valid_user_group_name(name))
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
                 return NSS_STATUS_NOTFOUND;
 
         /* Synthesize records for root and nobody, in case they are missing from /etc/group */
@@ -536,7 +536,7 @@ enum nss_status _nss_systemd_initgroups_dyn(
         assert(groupsp);
         assert(errnop);
 
-        if (!valid_user_group_name(user_name))
+        if (!valid_user_group_name(user_name, VALID_USER_RELAX))
                 return NSS_STATUS_NOTFOUND;
 
         /* Don't allow extending these two special users, the same as we won't resolve them via getpwnam() */
index d5ec32f07d927703020747758fd2d2eed61274d2..3c9520693de78a38e4cc8635c23c46500a176da8 100644 (file)
@@ -86,8 +86,8 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
                 { "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL,                           0,                                     0         },
                 { "matchHostname",  _JSON_VARIANT_TYPE_INVALID, NULL,                           0,                                     0         },
                 { "gid",            JSON_VARIANT_UNSIGNED,      json_dispatch_uid_gid,          offsetof(GroupRecord, gid),            0         },
-                { "members",        JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,  offsetof(GroupRecord, members),        0         },
-                { "administrators", JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,  offsetof(GroupRecord, administrators), 0         },
+                { "members",        JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,  offsetof(GroupRecord, members),        JSON_RELAX},
+                { "administrators", JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,  offsetof(GroupRecord, administrators), JSON_RELAX},
                 {},
         };
 
@@ -190,14 +190,14 @@ int group_record_load(
                 UserRecordLoadFlags load_flags) {
 
         static const JsonDispatch group_dispatch_table[] = {
-                { "groupName",      JSON_VARIANT_STRING,   json_dispatch_user_group_name,  offsetof(GroupRecord, group_name),       0         },
+                { "groupName",      JSON_VARIANT_STRING,   json_dispatch_user_group_name,  offsetof(GroupRecord, group_name),       JSON_RELAX},
                 { "realm",          JSON_VARIANT_STRING,   json_dispatch_realm,            offsetof(GroupRecord, realm),            0         },
                 { "disposition",    JSON_VARIANT_STRING,   json_dispatch_user_disposition, offsetof(GroupRecord, disposition),      0         },
                 { "service",        JSON_VARIANT_STRING,   json_dispatch_string,           offsetof(GroupRecord, service),          JSON_SAFE },
                 { "lastChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64,           offsetof(GroupRecord, last_change_usec), 0         },
                 { "gid",            JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid,          offsetof(GroupRecord, gid),              0         },
-                { "members",        JSON_VARIANT_ARRAY,    json_dispatch_user_group_list,  offsetof(GroupRecord, members),          0         },
-                { "administrators", JSON_VARIANT_ARRAY,    json_dispatch_user_group_list,  offsetof(GroupRecord, administrators),   0         },
+                { "members",        JSON_VARIANT_ARRAY,    json_dispatch_user_group_list,  offsetof(GroupRecord, members),          JSON_RELAX},
+                { "administrators", JSON_VARIANT_ARRAY,    json_dispatch_user_group_list,  offsetof(GroupRecord, administrators),   JSON_RELAX},
 
                 { "privileged",     JSON_VARIANT_OBJECT,   dispatch_privileged,            0,                                       0         },
 
index e28a5df19e2e4842a33a55e47f0a0e79635efd00..7a79acdee8b96119d357df85ec171fb0ad4f231b 100644 (file)
@@ -4107,7 +4107,7 @@ int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDi
                 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
 
         n = json_variant_string(variant);
-        if (!valid_user_group_name_compat(n))
+        if (!valid_user_group_name(n, FLAGS_SET(flags, JSON_RELAX) ? VALID_USER_RELAX : 0))
                 return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid user/group name.", strna(name));
 
         r = free_and_strdup(s, n);
index b9b300a1d2ab9e4527c28e0d11bc2143c2ef596e..a4e5b6f507bae7af81c858baa858e4dbe93ef5c8 100644 (file)
@@ -255,6 +255,7 @@ typedef enum JsonDispatchFlags {
         JSON_MANDATORY  = 1 << 1, /* Should existence of this property be mandatory? */
         JSON_LOG        = 1 << 2, /* Should the parser log about errors? */
         JSON_SAFE       = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */
+        JSON_RELAX      = 1 << 4, /* Use relaxed user name checking in json_dispatch_user_group_name */
 
         /* The following two may be passed into log_json() in addition to the three above */
         JSON_DEBUG      = 1 << 4, /* Indicates that this log message is a debug message */
index ff2cc411430e6e983c6341ead9907a756ee28e78..080c2a140d725d6151846d7bc77a6f0dc171330b 100644 (file)
@@ -600,7 +600,7 @@ int json_dispatch_user_group_list(const char *name, JsonVariant *variant, JsonDi
                 if (!json_variant_is_string(e))
                         return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
 
-                if (!valid_user_group_name_compat(json_variant_string(e)))
+                if (!valid_user_group_name(json_variant_string(e), FLAGS_SET(flags, JSON_RELAX) ? VALID_USER_RELAX : 0))
                         return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a valid user/group name: %s", json_variant_string(e));
 
                 r = strv_extend(&l, json_variant_string(e));
@@ -938,7 +938,7 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
                 { "imagePath",                  JSON_VARIANT_STRING,        json_dispatch_path,                offsetof(UserRecord, image_path),                    0         },
                 { "uid",                        JSON_VARIANT_UNSIGNED,      json_dispatch_uid_gid,             offsetof(UserRecord, uid),                           0         },
                 { "gid",                        JSON_VARIANT_UNSIGNED,      json_dispatch_uid_gid,             offsetof(UserRecord, gid),                           0         },
-                { "memberOf",                   JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,     offsetof(UserRecord, member_of),                     0         },
+                { "memberOf",                   JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,     offsetof(UserRecord, member_of),                     JSON_RELAX},
                 { "fileSystemType",             JSON_VARIANT_STRING,        json_dispatch_string,              offsetof(UserRecord, file_system_type),              JSON_SAFE },
                 { "partitionUuid",              JSON_VARIANT_STRING,        json_dispatch_id128,               offsetof(UserRecord, partition_uuid),                0         },
                 { "luksUuid",                   JSON_VARIANT_STRING,        json_dispatch_id128,               offsetof(UserRecord, luks_uuid),                     0         },
@@ -1231,7 +1231,7 @@ int user_group_record_mangle(
 int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_flags) {
 
         static const JsonDispatch user_dispatch_table[] = {
-                { "userName",                   JSON_VARIANT_STRING,        json_dispatch_user_group_name,     offsetof(UserRecord, user_name),                     0         },
+                { "userName",                   JSON_VARIANT_STRING,        json_dispatch_user_group_name,     offsetof(UserRecord, user_name),                     JSON_RELAX},
                 { "realm",                      JSON_VARIANT_STRING,        json_dispatch_realm,               offsetof(UserRecord, realm),                         0         },
                 { "realName",                   JSON_VARIANT_STRING,        json_dispatch_gecos,               offsetof(UserRecord, real_name),                     0         },
                 { "emailAddress",               JSON_VARIANT_STRING,        json_dispatch_string,              offsetof(UserRecord, email_address),                 JSON_SAFE },
@@ -1270,7 +1270,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
                 { "homeDirectory",              JSON_VARIANT_STRING,        json_dispatch_home_directory,      offsetof(UserRecord, home_directory),                0         },
                 { "uid",                        JSON_VARIANT_UNSIGNED,      json_dispatch_uid_gid,             offsetof(UserRecord, uid),                           0         },
                 { "gid",                        JSON_VARIANT_UNSIGNED,      json_dispatch_uid_gid,             offsetof(UserRecord, gid),                           0         },
-                { "memberOf",                   JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,     offsetof(UserRecord, member_of),                     0         },
+                { "memberOf",                   JSON_VARIANT_ARRAY,         json_dispatch_user_group_list,     offsetof(UserRecord, member_of),                     JSON_RELAX},
                 { "fileSystemType",             JSON_VARIANT_STRING,        json_dispatch_string,              offsetof(UserRecord, file_system_type),              JSON_SAFE },
                 { "partitionUuid",              JSON_VARIANT_STRING,        json_dispatch_id128,               offsetof(UserRecord, partition_uuid),                0         },
                 { "luksUuid",                   JSON_VARIANT_STRING,        json_dispatch_id128,               offsetof(UserRecord, luks_uuid),                     0         },
index 92f8796768d7f9a24df65ee88c9c267bfba84343..0769a792c228b914981c16e32708b3a87e3eaf6a 100644 (file)
@@ -587,7 +587,7 @@ int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
         _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
         int r;
 
-        if (!valid_user_group_name_compat(name))
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
                 return -EINVAL;
 
         r = json_build(&query, JSON_BUILD_OBJECT(
@@ -795,7 +795,7 @@ int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret) {
         _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
         int r;
 
-        if (!valid_user_group_name_compat(name))
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
                 return -EINVAL;
 
         r = json_build(&query, JSON_BUILD_OBJECT(
@@ -982,7 +982,7 @@ int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **r
 
         assert(ret);
 
-        if (!valid_user_group_name_compat(name))
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
                 return -EINVAL;
 
         r = json_build(&query, JSON_BUILD_OBJECT(
@@ -1025,7 +1025,7 @@ int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **
 
         assert(ret);
 
-        if (!valid_user_group_name_compat(name))
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
                 return -EINVAL;
 
         r = json_build(&query, JSON_BUILD_OBJECT(
index e8d26ab71b5632f8e9ff07df32e39469eb4bf174..162b650e649b22eb87ed05fec33d34ae2dcbd4b5 100644 (file)
@@ -158,6 +158,9 @@ _SD_BEGIN_DECLARATIONS;
 #define SD_MESSAGE_DNSSEC_DOWNGRADE       SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57)
 #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)
 
+#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)
+#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)
+
 _SD_END_DECLARATIONS;
 
 #endif
index f7cc7e09009e580bf46ba650a41a5e52f8122830..c3e5457fc171df647d1e799bd67a68a91cdcb7af 100644 (file)
@@ -1445,7 +1445,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                 if (r < 0)
                         log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s", fname, line, name);
 
-                if (!valid_user_group_name(resolved_name))
+                if (!valid_user_group_name(resolved_name, 0))
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                "[%s:%u] '%s' is not a valid user or group name.",
                                                fname, line, resolved_name);
@@ -1548,7 +1548,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                                                "[%s:%u] Lines of type 'm' require a group name in the third field.",
                                                fname, line);
 
-                if (!valid_user_group_name(resolved_id))
+                if (!valid_user_group_name(resolved_id, 0))
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                "[%s:%u] '%s' is not a valid user or group name.",
                                                fname, line, resolved_id);
@@ -1589,7 +1589,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                                 if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
                                         r = parse_gid(gid, &i->gid);
                                         if (r < 0) {
-                                                if (valid_user_group_name(gid))
+                                                if (valid_user_group_name(gid, 0))
                                                         i->group_name = TAKE_PTR(gid);
                                                 else
                                                         return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
index 11c01e5189139855ac19d160ba33e25e20d02683..a0e1495186f57e109a9d08576fdfac04004c0b25 100644 (file)
@@ -63,144 +63,163 @@ static void test_uid_ptr(void) {
         assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000);
 }
 
-static void test_valid_user_group_name_compat(void) {
+static void test_valid_user_group_name_relaxed(void) {
         log_info("/* %s */", __func__);
 
-        assert_se(!valid_user_group_name_compat(NULL));
-        assert_se(!valid_user_group_name_compat(""));
-        assert_se(!valid_user_group_name_compat("1"));
-        assert_se(!valid_user_group_name_compat("65535"));
-        assert_se(!valid_user_group_name_compat("-1"));
-        assert_se(!valid_user_group_name_compat("-kkk"));
-        assert_se(!valid_user_group_name_compat("rööt"));
-        assert_se(!valid_user_group_name_compat("."));
-        assert_se(!valid_user_group_name_compat(".eff"));
-        assert_se(!valid_user_group_name_compat("foo\nbar"));
-        assert_se(!valid_user_group_name_compat("0123456789012345678901234567890123456789"));
-        assert_se(!valid_user_group_name_or_id_compat("aaa:bbb"));
-        assert_se(!valid_user_group_name_compat("."));
-        assert_se(!valid_user_group_name_compat(".1"));
-        assert_se(!valid_user_group_name_compat(".65535"));
-        assert_se(!valid_user_group_name_compat(".-1"));
-        assert_se(!valid_user_group_name_compat(".-kkk"));
-        assert_se(!valid_user_group_name_compat(".rööt"));
-        assert_se(!valid_user_group_name_or_id_compat(".aaa:bbb"));
-
-        assert_se(valid_user_group_name_compat("root"));
-        assert_se(valid_user_group_name_compat("lennart"));
-        assert_se(valid_user_group_name_compat("LENNART"));
-        assert_se(valid_user_group_name_compat("_kkk"));
-        assert_se(valid_user_group_name_compat("kkk-"));
-        assert_se(valid_user_group_name_compat("kk-k"));
-        assert_se(valid_user_group_name_compat("eff.eff"));
-        assert_se(valid_user_group_name_compat("eff."));
-
-        assert_se(valid_user_group_name_compat("some5"));
-        assert_se(valid_user_group_name_compat("5some"));
-        assert_se(valid_user_group_name_compat("INNER5NUMBER"));
+        assert_se(!valid_user_group_name(NULL, VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("", VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("1", VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("65535", VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("-1", VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("foo\nbar", VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_RELAX|VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name(".aaa:bbb", VALID_USER_RELAX|VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name(".", VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("..", VALID_USER_RELAX));
+
+        assert_se(valid_user_group_name("root", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("lennart", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("LENNART", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("_kkk", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("kkk-", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("kk-k", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("eff.eff", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("eff.", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("-kkk", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("rööt", VALID_USER_RELAX));
+        assert_se(valid_user_group_name(".eff", VALID_USER_RELAX));
+        assert_se(valid_user_group_name(".1", VALID_USER_RELAX));
+        assert_se(valid_user_group_name(".65535", VALID_USER_RELAX));
+        assert_se(valid_user_group_name(".-1", VALID_USER_RELAX));
+        assert_se(valid_user_group_name(".-kkk", VALID_USER_RELAX));
+        assert_se(valid_user_group_name(".rööt", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("...", VALID_USER_RELAX));
+
+        assert_se(valid_user_group_name("some5", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("5some", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_RELAX));
+
+        assert_se(valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_RELAX));
+        assert_se(valid_user_group_name("Dāvis", VALID_USER_RELAX));
 }
 
 static void test_valid_user_group_name(void) {
         log_info("/* %s */", __func__);
 
-        assert_se(!valid_user_group_name(NULL));
-        assert_se(!valid_user_group_name(""));
-        assert_se(!valid_user_group_name("1"));
-        assert_se(!valid_user_group_name("65535"));
-        assert_se(!valid_user_group_name("-1"));
-        assert_se(!valid_user_group_name("-kkk"));
-        assert_se(!valid_user_group_name("rööt"));
-        assert_se(!valid_user_group_name("."));
-        assert_se(!valid_user_group_name(".eff"));
-        assert_se(!valid_user_group_name("foo\nbar"));
-        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789"));
-        assert_se(!valid_user_group_name_or_id("aaa:bbb"));
-        assert_se(!valid_user_group_name("."));
-        assert_se(!valid_user_group_name(".1"));
-        assert_se(!valid_user_group_name(".65535"));
-        assert_se(!valid_user_group_name(".-1"));
-        assert_se(!valid_user_group_name(".-kkk"));
-        assert_se(!valid_user_group_name(".rööt"));
-        assert_se(!valid_user_group_name_or_id(".aaa:bbb"));
-
-        assert_se(valid_user_group_name("root"));
-        assert_se(valid_user_group_name("lennart"));
-        assert_se(valid_user_group_name("LENNART"));
-        assert_se(valid_user_group_name("_kkk"));
-        assert_se(valid_user_group_name("kkk-"));
-        assert_se(valid_user_group_name("kk-k"));
-        assert_se(!valid_user_group_name("eff.eff"));
-        assert_se(!valid_user_group_name("eff."));
-
-        assert_se(valid_user_group_name("some5"));
-        assert_se(!valid_user_group_name("5some"));
-        assert_se(valid_user_group_name("INNER5NUMBER"));
+        assert_se(!valid_user_group_name(NULL, 0));
+        assert_se(!valid_user_group_name("", 0));
+        assert_se(!valid_user_group_name("1", 0));
+        assert_se(!valid_user_group_name("65535", 0));
+        assert_se(!valid_user_group_name("-1", 0));
+        assert_se(!valid_user_group_name("-kkk", 0));
+        assert_se(!valid_user_group_name("rööt", 0));
+        assert_se(!valid_user_group_name(".", 0));
+        assert_se(!valid_user_group_name(".eff", 0));
+        assert_se(!valid_user_group_name("foo\nbar", 0));
+        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", 0));
+        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name(".", 0));
+        assert_se(!valid_user_group_name("..", 0));
+        assert_se(!valid_user_group_name("...", 0));
+        assert_se(!valid_user_group_name(".1", 0));
+        assert_se(!valid_user_group_name(".65535", 0));
+        assert_se(!valid_user_group_name(".-1", 0));
+        assert_se(!valid_user_group_name(".-kkk", 0));
+        assert_se(!valid_user_group_name(".rööt", 0));
+        assert_se(!valid_user_group_name(".aaa:bbb", VALID_USER_ALLOW_NUMERIC));
+
+        assert_se(valid_user_group_name("root", 0));
+        assert_se(valid_user_group_name("lennart", 0));
+        assert_se(valid_user_group_name("LENNART", 0));
+        assert_se(valid_user_group_name("_kkk", 0));
+        assert_se(valid_user_group_name("kkk-", 0));
+        assert_se(valid_user_group_name("kk-k", 0));
+        assert_se(!valid_user_group_name("eff.eff", 0));
+        assert_se(!valid_user_group_name("eff.", 0));
+
+        assert_se(valid_user_group_name("some5", 0));
+        assert_se(!valid_user_group_name("5some", 0));
+        assert_se(valid_user_group_name("INNER5NUMBER", 0));
+
+        assert_se(!valid_user_group_name("piff.paff@ad.domain.example", 0));
+        assert_se(!valid_user_group_name("Dāvis", 0));
 }
 
-static void test_valid_user_group_name_or_id_compat(void) {
+static void test_valid_user_group_name_or_numeric_relaxed(void) {
         log_info("/* %s */", __func__);
 
-        assert_se(!valid_user_group_name_or_id_compat(NULL));
-        assert_se(!valid_user_group_name_or_id_compat(""));
-        assert_se(valid_user_group_name_or_id_compat("0"));
-        assert_se(valid_user_group_name_or_id_compat("1"));
-        assert_se(valid_user_group_name_or_id_compat("65534"));
-        assert_se(!valid_user_group_name_or_id_compat("65535"));
-        assert_se(valid_user_group_name_or_id_compat("65536"));
-        assert_se(!valid_user_group_name_or_id_compat("-1"));
-        assert_se(!valid_user_group_name_or_id_compat("-kkk"));
-        assert_se(!valid_user_group_name_or_id_compat("rööt"));
-        assert_se(!valid_user_group_name_or_id_compat("."));
-        assert_se(!valid_user_group_name_or_id_compat(".eff"));
-        assert_se(valid_user_group_name_or_id_compat("eff.eff"));
-        assert_se(valid_user_group_name_or_id_compat("eff."));
-        assert_se(!valid_user_group_name_or_id_compat("foo\nbar"));
-        assert_se(!valid_user_group_name_or_id_compat("0123456789012345678901234567890123456789"));
-        assert_se(!valid_user_group_name_or_id_compat("aaa:bbb"));
-
-        assert_se(valid_user_group_name_or_id_compat("root"));
-        assert_se(valid_user_group_name_or_id_compat("lennart"));
-        assert_se(valid_user_group_name_or_id_compat("LENNART"));
-        assert_se(valid_user_group_name_or_id_compat("_kkk"));
-        assert_se(valid_user_group_name_or_id_compat("kkk-"));
-        assert_se(valid_user_group_name_or_id_compat("kk-k"));
-
-        assert_se(valid_user_group_name_or_id_compat("some5"));
-        assert_se(valid_user_group_name_or_id_compat("5some"));
-        assert_se(valid_user_group_name_or_id_compat("INNER5NUMBER"));
+        assert_se(!valid_user_group_name(NULL, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("0", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("1", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("65534", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("65535", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("65536", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("-1", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("foo\nbar", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(!valid_user_group_name(".", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(!valid_user_group_name("..", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+
+        assert_se(valid_user_group_name("root", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("lennart", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("LENNART", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("_kkk", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("kkk-", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("kk-k", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("-kkk", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("rööt", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name(".eff", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("eff.eff", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("eff.", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("...", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+
+        assert_se(valid_user_group_name("some5", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("5some", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+
+        assert_se(valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
+        assert_se(valid_user_group_name("Dāvis", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX));
 }
 
-static void test_valid_user_group_name_or_id(void) {
+static void test_valid_user_group_name_or_numeric(void) {
         log_info("/* %s */", __func__);
 
-        assert_se(!valid_user_group_name_or_id(NULL));
-        assert_se(!valid_user_group_name_or_id(""));
-        assert_se(valid_user_group_name_or_id("0"));
-        assert_se(valid_user_group_name_or_id("1"));
-        assert_se(valid_user_group_name_or_id("65534"));
-        assert_se(!valid_user_group_name_or_id("65535"));
-        assert_se(valid_user_group_name_or_id("65536"));
-        assert_se(!valid_user_group_name_or_id("-1"));
-        assert_se(!valid_user_group_name_or_id("-kkk"));
-        assert_se(!valid_user_group_name_or_id("rööt"));
-        assert_se(!valid_user_group_name_or_id("."));
-        assert_se(!valid_user_group_name_or_id(".eff"));
-        assert_se(!valid_user_group_name_or_id("eff.eff"));
-        assert_se(!valid_user_group_name_or_id("eff."));
-        assert_se(!valid_user_group_name_or_id("foo\nbar"));
-        assert_se(!valid_user_group_name_or_id("0123456789012345678901234567890123456789"));
-        assert_se(!valid_user_group_name_or_id("aaa:bbb"));
-
-        assert_se(valid_user_group_name_or_id("root"));
-        assert_se(valid_user_group_name_or_id("lennart"));
-        assert_se(valid_user_group_name_or_id("LENNART"));
-        assert_se(valid_user_group_name_or_id("_kkk"));
-        assert_se(valid_user_group_name_or_id("kkk-"));
-        assert_se(valid_user_group_name_or_id("kk-k"));
-
-        assert_se(valid_user_group_name_or_id("some5"));
-        assert_se(!valid_user_group_name_or_id("5some"));
-        assert_se(valid_user_group_name_or_id("INNER5NUMBER"));
+        assert_se(!valid_user_group_name(NULL, VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("", VALID_USER_ALLOW_NUMERIC));
+        assert_se(valid_user_group_name("0", VALID_USER_ALLOW_NUMERIC));
+        assert_se(valid_user_group_name("1", VALID_USER_ALLOW_NUMERIC));
+        assert_se(valid_user_group_name("65534", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("65535", VALID_USER_ALLOW_NUMERIC));
+        assert_se(valid_user_group_name("65536", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("-1", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("-kkk", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("rööt", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name(".", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("..", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("...", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name(".eff", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("eff.eff", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("eff.", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("foo\nbar", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC));
+
+        assert_se(valid_user_group_name("root", VALID_USER_ALLOW_NUMERIC));
+        assert_se(valid_user_group_name("lennart", VALID_USER_ALLOW_NUMERIC));
+        assert_se(valid_user_group_name("LENNART", VALID_USER_ALLOW_NUMERIC));
+        assert_se(valid_user_group_name("_kkk", VALID_USER_ALLOW_NUMERIC));
+        assert_se(valid_user_group_name("kkk-", VALID_USER_ALLOW_NUMERIC));
+        assert_se(valid_user_group_name("kk-k", VALID_USER_ALLOW_NUMERIC));
+
+        assert_se(valid_user_group_name("some5", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("5some", VALID_USER_ALLOW_NUMERIC));
+        assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_ALLOW_NUMERIC));
+
+        assert_se(!valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_ALLOW_NUMERIC));
+        assert_se(!valid_user_group_name("Dāvis", VALID_USER_ALLOW_NUMERIC));
 }
 
 static void test_valid_gecos(void) {
@@ -355,10 +374,10 @@ int main(int argc, char *argv[]) {
         test_parse_uid();
         test_uid_ptr();
 
-        test_valid_user_group_name_compat();
+        test_valid_user_group_name_relaxed();
         test_valid_user_group_name();
-        test_valid_user_group_name_or_id_compat();
-        test_valid_user_group_name_or_id();
+        test_valid_user_group_name_or_numeric_relaxed();
+        test_valid_user_group_name_or_numeric();
         test_valid_gecos();
         test_valid_home();