Merge pull request #15352 from poettering/user-group-name-valdity-rework

user/group name validity rework
This commit is contained in:
Lennart Poettering 2020-04-09 18:49:22 +02:00 committed by GitHub
commit 9b3c65ed36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 517 additions and 259 deletions

View File

@ -412,3 +412,26 @@ as the best process to terminate and has been forcibly terminated by the
kernel.
Note that the memory pressure might or might not have been caused by @UNIT@.
-- b61fdac612e94b9182285b998843061f
Subject: Accepting user/group name @USER_GROUP_NAME@, which does not match strict user/group name rules.
Defined-By: systemd
Support: %SUPPORT_URL%
The user/group name @USER_GROUP_NAME@ has been specified, which is accepted
according the relaxed user/group name rules, but does not qualify under the
strict rules.
The strict user/group name rules written as regular expression are:
^[a-zA-Z_][a-zA-Z0-9_-]{0,30}$
The relaxed user/group name rules accept all names, except for the empty
string; names containing NUL bytes, control characters, colon or slash
characters; names not valid UTF-8; names with leading or trailing whitespace;
the strings "." or ".."; fully numeric strings, or strings beginning in a
hyphen and otherwise fully numeric.
For further details on strict and relaxed user/group name rules, see:
https://systemd.io/USER_NAMES

169
docs/USER_NAMES.md Normal file
View File

@ -0,0 +1,169 @@
--
title: User/Group Name Syntax
category: Concepts
layout: default
---
# User/Group Name Syntax
The precise set of allowed user and group names on Linux systems is weakly
defined. Depending on the distribution a different set of requirements and
restrictions on the syntax of user/group names are enforced — on some
distributions the accepted syntax is even configurable by the administrator. In
the interest of interoperability systemd enforces different rules when
processing users/group defined by other subsystems and when defining users/groups
itself, following the principle of "Be conservative in what you send, be
liberal in what you accept". Also in the interest of interoperability systemd
will enforce the same rules everywhere and not make them configurable or
distribution dependent. The precise rules are described below.
Generally, the same rules apply for user as for group names.
## Other Systems
* On POSIX the set of [valid user
names](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_437)
is defined as [lower and upper case ASCII letters, digits, period,
underscore, and
hyphen](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282),
with the restriction that hyphen is now allowed as first character of the
user name. Interestingly no size limit is declared, i.e. in neither
direction, meaning that strictly speaking according to POSIX both the empty
string is a valid user name as well as a string of gigabytes in length.
* Debian/Ubuntu based systems enforce the regular expression
`^[a-z][-a-z0-9]*$`, i.e. only lower case ASCII letters, digits and
hyphens. As first character only lowercase ASCII letters are allowed. This
regular expression is configurable by the administrator at runtime
though. This rule enforces a minimum length of one character but no maximum
length.
* Upstream shadow-utils enforces the regular expression
`^[a-z_][a-z0-9_-]*[$]$`, i.e. is similar to the Debian/Ubuntu rule, but
allows underscores and hyphens, but the latter not as first character. Also,
an optional trailing dollar character is permitted.
* Fedora/Red Hat based systems enforce the regular expression of
`^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,30}[a-zA-Z0-9_.$-]?$`, i.e. a size limit of
32 characters, with upper and lower case letters, digits, underscores,
hyphens and periods. No hyphen as first character though, and the last
character may be a dollar character. On top of that, `.` and `..` are not
allowed as user/group names.
* sssd is known to generate user names with embedded `@` and white-space
characters, as well as non-ASCII (i.e. UTF-8) user/group names.
* winbindd is known to generate user/group names with embedded `\` and
white-space characters, as well as non-ASCII (i.e. UTF-8) user/group names.
Other operating systems enforce different rules; in this documentation we'll
focus on Linux systems only however, hence those are out of scope. That said,
software like Samba is frequently deployed on Linux for providing compatibility
with Windows systems; on such systems it might be wise to stick to user/group
names also valid according to Windows rules.
## Rules systemd enforces
Distilled from the above, below are the rules systemd enforces on user/group
names. An additional, common rule between both modes listed below is that empty
strings are not valid user/group names.
Philosophically, the strict mode described below enforces a white-list of what's
allowed and prohibits everything else, while the relaxed mode described below
implements a blacklist of what's not allowed and permits everything else.
### Strict mode
Strict user/group name syntax is enforced whenever a systemd component is used
to register a user or group in the system, for example a system user/group
using
[`systemd-sysusers.service`](https://www.freedesktop.org/software/systemd/man/systemd-sysusers.html)
or a regular user with
[`systemd-homed.service`](https://www.freedesktop.org/software/systemd/man/systemd-homed.html).
In strict mode, only uppercase and lowercase characters are allowed, as well as
digits, underscores and hyphens. The first character may not be a digit or
hyphen. A size limit is enforced: the minimum of `sysconf(_SC_LOGIN_NAME_MAX)`
(typically 256 on Linux; rationale: this is how POSIX suggests to detect the
limit), `UT_NAMESIZE-1` (typically 31 on Linux; rationale: names longer than
this cannot correctly appear in `utmp`/`wtmp` and create ambiguity with login
accounting) and `FILENAME_MAX` (4096 on Linux; rationale: user names typically
appear in directory names, i.e. the home directory), thus MIN(256, 31, 4096) =
31.
Note that these rules are both more strict and more relaxed than all of the
rules enforced by other systems listed above. A user/group name conforming to
systemd's strict rules will not necessarily pass a test by the rules enforced
by these other subsystems.
Written as regular expression the above is: `^[a-zA-Z_][a-zA-Z0-9_-]{0,30}$`
### Relaxed mode
Relaxed user/group name syntax is enforced whenever a systemd component accepts
and makes use of user/group names registered by other (non-systemd)
components of the system, for example in
[`systemd-logind.service`](https://www.freedesktop.org/software/systemd/man/systemd-logind.html).
Relaxed syntax is also enforced by the `User=` setting in service unit files,
i.e. for system services used for running services. Since these users may be
registered by a variety of tools relaxed mode is used, but since the primary
purpose of these users is to run a system service and thus a job for systemd a
warning is shown if the specified user name does not qualify by the strict
rules above.
* No embedded NUL bytes (rationale: handling in C must be possible and
straight-forward)
* No names consisting fully of digits (rationale: avoid confusion with numeric
UID/GID specifications)
* Similar, no names consisting of an initial hyphen and otherwise entirely made
up of digits (rationale: avoid confusion with negative, numeric UID/GID
specifications, e.g. `-1`)
* No strings that do not qualify as valid UTF-8 (rationale: we want to be able
to embed these strings in JSON, with permits only valid UTF-8 in its strings;
user names using other character sets, such as JIS/Shift-JIS will cause
validation errors)
* No control characters (i.e. characters in ASCII range 1…31; rationale: they
tend to have special meaning when output on a terminal in other contexts,
moreover the newline character — as a specific control character — is used as
record separator in `/etc/passwd`, and hence it's crucial to avoid
ambiguities here)
* No colon characters (rationale: it is used as field separator in `/etc/passwd`)
* The two strings `.` and `..` are not permitted, as these have special meaning
in file system paths, and user names are frequently included in file system
paths, in particular for the purpose of home directories.
* Similar, no slashes, as these have special meaning in file system paths
* No leading or trailing white-space is permitted; and hence no user/group names
consisting of white-space only either (rationale: this typically indicates
parsing errors, and creates confusion since not visible on screen)
Note that these relaxed rules are implied by the strict rules above, i.e. all
user/group names accepted by the strict rules are also accepted by the relaxed
rules, but not vice versa.
Note that this relaxed mode does not refuse a couple of very questionable
syntaxes. For example it permits a leading or embedded period. A leading period
is problematic because the matching home directory would typically be hidden
from the user's/administrator's view. An embedded period is problematic since
it creates ambiguity in traditional `chown` syntax (which is still accepted
today) that uses it to separate user and group names in the command's
parameter: without consulting the user/group databases it is not possible to
determine if a `chown` invocation would change just the owning user or both the
owning user and group. It also allows embeddeding `@` (which is confusing to
MTAs).
## Common Core
Combining all rules listed above, user/group names that shall be considered
valid in all systemd contexts and on all Linux systems should match the
following regular expression (at least according to our understanding):
`^[a-z][a-z0-9-]{0,30}$`

View File

@ -205,7 +205,8 @@ object. The following fields are currently defined:
UNIX user name. This field is the only mandatory field, all others are
optional. Corresponds with the `pw_name` field of of `struct passwd` and the
`sp_namp` field of `struct spwd` (i.e. the shadow user record stored in
`/etc/shadow`).
`/etc/shadow`). See [User/Group Name Syntax](https://systemd.io/USER_NAMES) for
the (relaxed) rules the various systemd components enforce on user/group names.
`realm` → The "realm" a user is defined in. This concept allows distinguishing
users with the same name that originate in different organizations or

View File

@ -677,7 +677,10 @@
<listitem><para>Create a new home directory/user account of the specified name. Use the various
user record property options (as documented above) to control various aspects of the home directory
and its user accounts.</para></listitem>
and its user accounts.</para>
<para>The specified user name should follow the strict syntax described on <ulink
url="https://systemd.io/USER_NAMES">User/Group Name Syntax</ulink>.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -217,12 +217,15 @@
is set, the default group of the user is used. This setting does not affect commands whose command line is
prefixed with <literal>+</literal>.</para>
<para>Note that restrictions on the user/group name syntax are enforced: the specified name must consist only
of the characters a-z, A-Z, 0-9, <literal>_</literal> and <literal>-</literal>, except for the first character
which must be one of a-z, A-Z or <literal>_</literal> (i.e. numbers and <literal>-</literal> are not permitted
as first character). The user/group name must have at least one character, and at most 31. These restrictions
are enforced in order to avoid ambiguities and to ensure user/group names and unit files remain portable among
Linux systems.</para>
<para>Note that this enforces only weak restrictions on the user/group name syntax, but will generate
warnings in many cases where user/group names do not adhere to the following rules: the specified
name should consist only of the characters a-z, A-Z, 0-9, <literal>_</literal> and
<literal>-</literal>, except for the first character which must be one of a-z, A-Z and
<literal>_</literal> (i.e. digits and <literal>-</literal> are not permitted as first character). The
user/group name must have at least one character, and at most 31. These restrictions are made in
order to avoid ambiguities and to ensure user/group names and unit files remain portable among Linux
systems. For further details on the names accepted and the names warned about see <ulink
url="https://systemd.io/USER_NAMES">User/Group Name Syntax</ulink>.</para>
<para>When used in conjunction with <varname>DynamicUser=</varname> the user/group name specified is
dynamically allocated at the time the service is started, and released at the time the service is

View File

@ -154,6 +154,9 @@ r - 500-900
A-Z or <literal>_</literal> (i.e. numbers and <literal>-</literal> are not permitted as first character). The
user/group name must have at least one character, and at most 31.</para>
<para>For further details about the syntax of user/group names, see <ulink
url="https://systemd.io/USER_NAMES">User/Group Name Syntax</ulink>.</para>
<para>It is strongly recommended to pick user and group names that are unlikely to clash with normal users
created by the administrator. A good scheme to guarantee this is by prefixing all system and group names with the
underscore, and avoiding too generic names.</para>

View 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,92 +701,125 @@ 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:
/* 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.
*
* - 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
*
* 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;
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);
bool only_digits_seen = u[0] >= '0' && u[0] <= '9';
if (FLAGS_SET(flags, VALID_USER_RELAX)) {
if (only_digits_seen) {
log_warning("User or group name \"%s\" starts with a digit, accepting for compatibility.", u);
warned = true;
/* 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 (!utf8_is_valid(u)) /* We want to synthesize JSON from this, hence insist on UTF-8 */
return false;
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;
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 (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;
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 (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;
/* 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);
/* 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;
for (i = u+1; *i; i++)
if (!(*i >= 'a' && *i <= 'z') &&
!(*i >= 'A' && *i <= 'Z') &&
!(*i >= '0' && *i <= '9') &&
!IN_SET(*i, '_', '-'))
return false;
l = i - u;
sz = sysconf(_SC_LOGIN_NAME_MAX);
assert_se(sz > 0);
if (l > (size_t) sz)
return false;
if (l > FILENAME_MAX)
return false;
if (l > UT_NAMESIZE - 1)
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;
}
return false;
}
if (only_digits_seen)
return false;
sz = sysconf(_SC_LOGIN_NAME_MAX);
assert_se(sz > 0);
if ((size_t) (i-u) > (size_t) sz)
return false;
if ((size_t) (i-u) > UT_NAMESIZE - 1)
return false;
return true;
}
bool valid_user_group_name_or_id_full(const char *u, bool strict) {
/* 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. */
if (isempty(u))
return false;
if (parse_uid(u, NULL) >= 0)
return true;
return valid_user_group_name_full(u, strict);
}
bool valid_gecos(const char *d) {
if (!d)

View 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);

View File

@ -1248,10 +1248,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);
@ -1436,7 +1436,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");

View 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);

View 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);

View 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(

View 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);

View 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)

View File

@ -2107,7 +2107,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;
}
@ -2161,7 +2161,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;
}

View File

@ -4295,7 +4295,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;
}

View 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

View 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"));

View 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);

View 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 */

View 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() */

View 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 },

View 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);

View 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 */

View 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 },

View 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(

View 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

View 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);

View 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(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_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("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_compat("some5"));
assert_se(valid_user_group_name_compat("5some"));
assert_se(valid_user_group_name_compat("INNER5NUMBER"));
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(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"));
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("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"));
assert_se(!valid_user_group_name("5some"));
assert_se(valid_user_group_name("INNER5NUMBER"));
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(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_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("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_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("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(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_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("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_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("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();

View File

@ -541,16 +541,15 @@ static int ssh_authorized_keys(int argc, char *argv[], void *userdata) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
int r;
if (!valid_user_group_name(argv[1]))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name '%s'.", argv[1]);
r = userdb_by_name(argv[1], arg_userdb_flags, &ur);
if (r == -ESRCH)
log_error_errno(r, "User %s does not exist.", argv[1]);
return log_error_errno(r, "User %s does not exist.", argv[1]);
else if (r == -EHOSTDOWN)
log_error_errno(r, "Selected user database service is not available for this request.");
return log_error_errno(r, "Selected user database service is not available for this request.");
else if (r == -EINVAL)
return log_error_errno(r, "Failed to find user %s: %m (Invalid user name?)", argv[1]);
else if (r < 0)
log_error_errno(r, "Failed to find user %s: %m", argv[1]);
return log_error_errno(r, "Failed to find user %s: %m", argv[1]);
if (strv_isempty(ur->ssh_authorized_keys))
log_debug("User record for %s has no public SSH keys.", argv[1]);