summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/systemd-sysusers.xml27
-rw-r--r--man/systemd.unit.xml2
-rw-r--r--src/basic/user-util.c116
-rw-r--r--src/basic/user-util.h3
-rw-r--r--src/firstboot/firstboot.c2
-rw-r--r--src/nss-systemd/nss-systemd.c60
-rw-r--r--src/sysusers/sysusers.c272
-rw-r--r--src/test/test-user-util.c4
-rw-r--r--test/test-execute/exec-specifier.service2
-rw-r--r--test/test-execute/exec-specifier@.service2
-rwxr-xr-xtest/test-sysusers.sh.in2
-rw-r--r--test/test-sysusers/unhappy-1.expected-err2
12 files changed, 261 insertions, 233 deletions
diff --git a/man/systemd-sysusers.xml b/man/systemd-sysusers.xml
index 9011cdb755..b399b3b04c 100644
--- a/man/systemd-sysusers.xml
+++ b/man/systemd-sysusers.xml
@@ -35,23 +35,22 @@
<refsect1>
<title>Description</title>
- <para><command>systemd-sysusers</command> creates system users and
- groups, based on the file format and location specified in
+ <para><command>systemd-sysusers</command> creates system users and groups, based on files in the format
+ described in
<citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para>
- <para>If invoked with no arguments, it applies all directives from all files
- found in the directories specified by
- <citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
- When invoked with positional arguments, if option
- <option>--replace=<replaceable>PATH</replaceable></option> is specified, arguments
- specified on the command line are used instead of the configuration file
- <replaceable>PATH</replaceable>. Otherwise, just the configuration specified by
- the command line arguments is executed. The string <literal>-</literal> may be
- specified instead of a filename to instruct <command>systemd-sysusers</command>
- to read the configuration from standard input. If only the basename of a file is
- specified, all configuration directories are searched for a matching file and
- the file found that has the highest priority is executed.</para>
+ <para>If invoked with no arguments, it applies all directives from all files found in the directories
+ specified by
+ <citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>. When
+ invoked with positional arguments, if option <option>--replace=<replaceable>PATH</replaceable></option>
+ is specified, arguments specified on the command line are used instead of the configuration file
+ <replaceable>PATH</replaceable>. Otherwise, just the configuration specified by the command line
+ arguments is executed. The string <literal>-</literal> may be specified instead of a filename to instruct
+ <command>systemd-sysusers</command> to read the configuration from standard input. If the argument is a
+ relative path, all configuration directories are searched for a matching file and the file found that has
+ the highest priority is executed. If the argument is an absolute path, that file is used directly without
+ searching of the configuration directories.</para>
</refsect1>
<refsect1>
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index 767c7186cd..95f1b98cbd 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -2108,7 +2108,7 @@ Note that this setting is <emphasis>not</emphasis> influenced by the <varname>Us
<row>
<entry><literal>%s</literal></entry>
<entry>User shell</entry>
- <entry>This is the shell of the user running the service manager instance. In case of the system manager this resolves to <literal>/bin/sh</literal>.</entry>
+ <entry>This is the shell of the user running the service manager instance.</entry>
</row>
<row>
<entry><literal>%S</literal></entry>
diff --git a/src/basic/user-util.c b/src/basic/user-util.c
index 16185332f9..80f9ff144b 100644
--- a/src/basic/user-util.c
+++ b/src/basic/user-util.c
@@ -13,6 +13,7 @@
#include "sd-messages.h"
#include "alloc-util.h"
+#include "chase-symlinks.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
@@ -136,7 +137,6 @@ char *getusername_malloc(void) {
}
bool is_nologin_shell(const char *shell) {
-
return PATH_IN_SET(shell,
/* 'nologin' is the friendliest way to disable logins for a user account. It prints a nice
* message and exits. Different distributions place the binary at different places though,
@@ -154,6 +154,21 @@ bool is_nologin_shell(const char *shell) {
"/usr/bin/true");
}
+const char* default_root_shell(const char *root) {
+ /* We want to use the preferred shell, i.e. DEFAULT_USER_SHELL, which usually
+ * will be /bin/bash. Fall back to /bin/sh if DEFAULT_USER_SHELL is not found,
+ * or any access errors. */
+
+ int r = chase_symlinks(DEFAULT_USER_SHELL, root, CHASE_PREFIX_ROOT, NULL, NULL);
+ if (r < 0 && r != -ENOENT)
+ log_debug_errno(r, "Failed to look up shell '%s%s%s': %m",
+ strempty(root), root ? "/" : "", DEFAULT_USER_SHELL);
+ if (r > 0)
+ return DEFAULT_USER_SHELL;
+
+ return "/bin/sh";
+}
+
static int synthesize_user_creds(
const char **username,
uid_t *uid, gid_t *gid,
@@ -176,13 +191,13 @@ static int synthesize_user_creds(
*home = "/root";
if (shell)
- *shell = "/bin/sh";
+ *shell = default_root_shell(NULL);
return 0;
}
- if (synthesize_nobody() &&
- STR_IN_SET(*username, NOBODY_USER_NAME, "65534")) {
+ if (STR_IN_SET(*username, NOBODY_USER_NAME, "65534") &&
+ synthesize_nobody()) {
*username = NOBODY_USER_NAME;
if (uid)
@@ -326,8 +341,8 @@ int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) {
return 0;
}
- if (synthesize_nobody() &&
- STR_IN_SET(*groupname, NOBODY_GROUP_NAME, "65534")) {
+ if (STR_IN_SET(*groupname, NOBODY_GROUP_NAME, "65534") &&
+ synthesize_nobody()) {
*groupname = NOBODY_GROUP_NAME;
if (gid)
@@ -373,8 +388,7 @@ char* uid_to_name(uid_t uid) {
/* Shortcut things to avoid NSS lookups */
if (uid == 0)
return strdup("root");
- if (synthesize_nobody() &&
- uid == UID_NOBODY)
+ if (uid == UID_NOBODY && synthesize_nobody())
return strdup(NOBODY_USER_NAME);
if (uid_is_valid(uid)) {
@@ -417,8 +431,7 @@ char* gid_to_name(gid_t gid) {
if (gid == 0)
return strdup("root");
- if (synthesize_nobody() &&
- gid == GID_NOBODY)
+ if (gid == GID_NOBODY && synthesize_nobody())
return strdup(NOBODY_GROUP_NAME);
if (gid_is_valid(gid)) {
@@ -556,43 +569,29 @@ int getgroups_alloc(gid_t** gids) {
return ngroups;
}
-int get_home_dir(char **_h) {
+int get_home_dir(char **ret) {
struct passwd *p;
const char *e;
char *h;
uid_t u;
- assert(_h);
+ assert(ret);
/* Take the user specified one */
e = secure_getenv("HOME");
- if (e && path_is_valid(e) && path_is_absolute(e)) {
- h = strdup(e);
- if (!h)
- return -ENOMEM;
-
- *_h = path_simplify(h);
- return 0;
- }
+ if (e && path_is_valid(e) && path_is_absolute(e))
+ goto found;
/* Hardcode home directory for root and nobody to avoid NSS */
u = getuid();
if (u == 0) {
- h = strdup("/root");
- if (!h)
- return -ENOMEM;
-
- *_h = h;
- return 0;
+ e = "/root";
+ goto found;
}
- if (synthesize_nobody() &&
- u == UID_NOBODY) {
- h = strdup("/");
- if (!h)
- return -ENOMEM;
- *_h = h;
- return 0;
+ if (u == UID_NOBODY && synthesize_nobody()) {
+ e = "/";
+ goto found;
}
/* Check the database... */
@@ -600,56 +599,42 @@ int get_home_dir(char **_h) {
p = getpwuid(u);
if (!p)
return errno_or_else(ESRCH);
+ e = p->pw_dir;
- if (!path_is_valid(p->pw_dir) ||
- !path_is_absolute(p->pw_dir))
+ if (!path_is_valid(e) || !path_is_absolute(e))
return -EINVAL;
- h = strdup(p->pw_dir);
+ found:
+ h = strdup(e);
if (!h)
return -ENOMEM;
- *_h = path_simplify(h);
+ *ret = path_simplify(h);
return 0;
}
-int get_shell(char **_s) {
+int get_shell(char **ret) {
struct passwd *p;
const char *e;
char *s;
uid_t u;
- assert(_s);
+ assert(ret);
/* Take the user specified one */
e = secure_getenv("SHELL");
- if (e && path_is_valid(e) && path_is_absolute(e)) {
- s = strdup(e);
- if (!s)
- return -ENOMEM;
-
- *_s = path_simplify(s);
- return 0;
- }
+ if (e && path_is_valid(e) && path_is_absolute(e))
+ goto found;
/* Hardcode shell for root and nobody to avoid NSS */
u = getuid();
if (u == 0) {
- s = strdup("/bin/sh");
- if (!s)
- return -ENOMEM;
-
- *_s = s;
- return 0;
+ e = default_root_shell(NULL);
+ goto found;
}
- if (synthesize_nobody() &&
- u == UID_NOBODY) {
- s = strdup(NOLOGIN);
- if (!s)
- return -ENOMEM;
-
- *_s = s;
- return 0;
+ if (u == UID_NOBODY && synthesize_nobody()) {
+ e = NOLOGIN;
+ goto found;
}
/* Check the database... */
@@ -657,16 +642,17 @@ int get_shell(char **_s) {
p = getpwuid(u);
if (!p)
return errno_or_else(ESRCH);
+ e = p->pw_shell;
- if (!path_is_valid(p->pw_shell) ||
- !path_is_absolute(p->pw_shell))
+ if (!path_is_valid(e) || !path_is_absolute(e))
return -EINVAL;
- s = strdup(p->pw_shell);
+ found:
+ s = strdup(e);
if (!s)
return -ENOMEM;
- *_s = path_simplify(s);
+ *ret = path_simplify(s);
return 0;
}
diff --git a/src/basic/user-util.h b/src/basic/user-util.h
index e1692c4f66..614dec2fde 100644
--- a/src/basic/user-util.h
+++ b/src/basic/user-util.h
@@ -55,7 +55,7 @@ int merge_gid_lists(const gid_t *list1, size_t size1, const gid_t *list2, size_t
int getgroups_alloc(gid_t** gids);
int get_home_dir(char **ret);
-int get_shell(char **_ret);
+int get_shell(char **ret);
int reset_uid_gid(void);
@@ -130,6 +130,7 @@ int putsgent_sane(const struct sgrp *sg, FILE *stream);
#endif
bool is_nologin_shell(const char *shell);
+const char* default_root_shell(const char *root);
int is_this_me(const char *username);
diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c
index 9169129a68..fd9954b54d 100644
--- a/src/firstboot/firstboot.c
+++ b/src/firstboot/firstboot.c
@@ -755,7 +755,7 @@ static int write_root_passwd(const char *passwd_path, const char *password, cons
.pw_gid = 0,
.pw_gecos = (char *) "Super User",
.pw_dir = (char *) "/root",
- .pw_shell = (char *) (shell ?: "/bin/sh"),
+ .pw_shell = (char *) (shell ?: default_root_shell(arg_root)),
};
if (errno != ENOENT)
diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c
index e24828450f..75d749e736 100644
--- a/src/nss-systemd/nss-systemd.c
+++ b/src/nss-systemd/nss-systemd.c
@@ -26,7 +26,7 @@ static const struct passwd root_passwd = {
.pw_gid = 0,
.pw_gecos = (char*) "Super User",
.pw_dir = (char*) "/root",
- .pw_shell = (char*) "/bin/sh",
+ .pw_shell = NULL,
};
static const struct spwd root_spwd = {
@@ -142,24 +142,25 @@ NSS_INITGROUPS_PROTOTYPE(systemd);
static enum nss_status copy_synthesized_passwd(
struct passwd *dest,
const struct passwd *src,
+ const char *fallback_shell,
char *buffer, size_t buflen,
int *errnop) {
- size_t required;
-
assert(dest);
assert(src);
assert(src->pw_name);
assert(src->pw_passwd);
assert(src->pw_gecos);
assert(src->pw_dir);
- assert(src->pw_shell);
- required = strlen(src->pw_name) + 1;
- required += strlen(src->pw_passwd) + 1;
- required += strlen(src->pw_gecos) + 1;
- required += strlen(src->pw_dir) + 1;
- required += strlen(src->pw_shell) + 1;
+ const char *shell = ASSERT_PTR(src->pw_shell ?: fallback_shell);
+
+ size_t required =
+ strlen(src->pw_name) + 1 +
+ strlen(src->pw_passwd) + 1 +
+ strlen(src->pw_gecos) + 1 +
+ strlen(src->pw_dir) + 1 +
+ strlen(shell) + 1;
if (buflen < required) {
*errnop = ERANGE;
@@ -176,7 +177,7 @@ static enum nss_status copy_synthesized_passwd(
dest->pw_gecos = stpcpy(dest->pw_passwd, src->pw_passwd) + 1;
dest->pw_dir = stpcpy(dest->pw_gecos, src->pw_gecos) + 1;
dest->pw_shell = stpcpy(dest->pw_dir, src->pw_dir) + 1;
- strcpy(dest->pw_shell, src->pw_shell);
+ strcpy(dest->pw_shell, shell);
return NSS_STATUS_SUCCESS;
}
@@ -187,15 +188,14 @@ static enum nss_status copy_synthesized_spwd(
char *buffer, size_t buflen,
int *errnop) {
- size_t required;
-
assert(dest);
assert(src);
assert(src->sp_namp);
assert(src->sp_pwdp);
- required = strlen(src->sp_namp) + 1;
- required += strlen(src->sp_pwdp) + 1;
+ size_t required =
+ strlen(src->sp_namp) + 1 +
+ strlen(src->sp_pwdp) + 1;
if (buflen < required) {
*errnop = ERANGE;
@@ -220,8 +220,6 @@ static enum nss_status copy_synthesized_group(
char *buffer, size_t buflen,
int *errnop) {
- size_t required;
-
assert(dest);
assert(src);
assert(src->gr_name);
@@ -229,9 +227,10 @@ static enum nss_status copy_synthesized_group(
assert(src->gr_mem);
assert(!*src->gr_mem); /* Our synthesized records' gr_mem is always just NULL... */
- required = strlen(src->gr_name) + 1;
- required += strlen(src->gr_passwd) + 1;
- required += sizeof(char*); /* ...but that NULL still needs to be stored into the buffer! */
+ size_t required =
+ strlen(src->gr_name) + 1 +
+ strlen(src->gr_passwd) + 1 +
+ sizeof(char*); /* ...but that NULL still needs to be stored into the buffer! */
if (buflen < ALIGN(required)) {
*errnop = ERANGE;
@@ -257,15 +256,14 @@ static enum nss_status copy_synthesized_sgrp(
char *buffer, size_t buflen,
int *errnop) {
- size_t required;
-
assert(dest);
assert(src);
assert(src->sg_namp);
assert(src->sg_passwd);
- required = strlen(src->sg_namp) + 1;
- required += strlen(src->sg_passwd) + 1;
+ size_t required =
+ strlen(src->sg_namp) + 1 +
+ strlen(src->sg_passwd) + 1;
if (buflen < required) {
*errnop = ERANGE;
@@ -310,13 +308,17 @@ enum nss_status _nss_systemd_getpwnam_r(
if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
if (streq(name, root_passwd.pw_name))
- return copy_synthesized_passwd(pwd, &root_passwd, buffer, buflen, errnop);
+ return copy_synthesized_passwd(pwd, &root_passwd,
+ default_root_shell(NULL),
+ buffer, buflen, errnop);
if (streq(name, nobody_passwd.pw_name)) {
if (!synthesize_nobody())
return NSS_STATUS_NOTFOUND;
- return copy_synthesized_passwd(pwd, &nobody_passwd, buffer, buflen, errnop);
+ return copy_synthesized_passwd(pwd, &nobody_passwd,
+ NULL,
+ buffer, buflen, errnop);
}
} else if (STR_IN_SET(name, root_passwd.pw_name, nobody_passwd.pw_name))
@@ -354,13 +356,17 @@ enum nss_status _nss_systemd_getpwuid_r(
if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
if (uid == root_passwd.pw_uid)
- return copy_synthesized_passwd(pwd, &root_passwd, buffer, buflen, errnop);
+ return copy_synthesized_passwd(pwd, &root_passwd,
+ default_root_shell(NULL),
+ buffer, buflen, errnop);
if (uid == nobody_passwd.pw_uid) {
if (!synthesize_nobody())
return NSS_STATUS_NOTFOUND;
- return copy_synthesized_passwd(pwd, &nobody_passwd, buffer, buflen, errnop);
+ return copy_synthesized_passwd(pwd, &nobody_passwd,
+ NULL,
+ buffer, buflen, errnop);
}
} else if (uid == root_passwd.pw_uid || uid == nobody_passwd.pw_uid)
diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c
index 9c1abf984e..14f5ac3246 100644
--- a/src/sysusers/sysusers.c
+++ b/src/sysusers/sysusers.c
@@ -4,6 +4,7 @@
#include <utmp.h>
#include "alloc-util.h"
+#include "chase-symlinks.h"
#include "conf-files.h"
#include "copy.h"
#include "creds-util.h"
@@ -27,6 +28,7 @@
#include "set.h"
#include "smack-util.h"
#include "specifier.h"
+#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "sync-util.h"
@@ -390,8 +392,14 @@ static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) {
}
#endif
-static const char* default_shell(uid_t uid) {
- return uid == 0 ? "/bin/sh" : NOLOGIN;
+static const char* pick_shell(const Item *i) {
+ if (i->type != ADD_USER)
+ return NULL;
+ if (i->shell)
+ return i->shell;
+ if (i->uid_set && i->uid == 0)
+ return default_root_shell(arg_root);
+ return NOLOGIN;
}
static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char **tmpfile_path) {
@@ -473,7 +481,7 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
/* Initialize the shell to nologin, with one exception:
* for root we patch in something special */
- .pw_shell = i->shell ?: (char*) default_shell(i->uid),
+ .pw_shell = (char*) pick_shell(i),
};
/* Try to pick up the shell for this account via the credentials logic */
@@ -659,14 +667,14 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp);
if (r < 0)
- return log_debug_errno(r, "Failed to open temporary copy of %s: %m", group_path);
+ return log_error_errno(r, "Failed to open temporary copy of %s: %m", group_path);
original = fopen(group_path, "re");
if (original) {
r = copy_rights_with_fallback(fileno(original), fileno(group), group_tmp);
if (r < 0)
- return log_debug_errno(r, "Failed to copy permissions from %s to %s: %m",
+ return log_error_errno(r, "Failed to copy permissions from %s to %s: %m",
group_path, group_tmp);
while ((r = fgetgrent_sane(original, &gr)) > 0) {
@@ -692,19 +700,19 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
r = putgrent_with_members(gr, group);
if (r < 0)
- return log_debug_errno(r, "Failed to add existing group \"%s\" to temporary group file: %m",
+ return log_error_errno(r, "Failed to add existing group \"%s\" to temporary group file: %m",
gr->gr_name);
if (r > 0)
group_changed = true;
}
if (r < 0)
- return log_debug_errno(r, "Failed to read %s: %m", group_path);
+ return log_error_errno(r, "Failed to read %s: %m", group_path);
} else {
if (errno != ENOENT)
- return log_debug_errno(errno, "Failed to open %s: %m", group_path);
+ return log_error_errno(errno, "Failed to open %s: %m", group_path);
if (fchmod(fileno(group), 0644) < 0)
- return log_debug_errno(errno, "Failed to fchmod %s: %m", group_tmp);
+ return log_error_errno(errno, "Failed to fchmod %s: %m", group_tmp);
}
ORDERED_HASHMAP_FOREACH(i, todo_gids) {
@@ -716,7 +724,7 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
r = putgrent_with_members(&n, group);
if (r < 0)
- return log_debug_errno(r, "Failed to add new group \"%s\" to temporary group file: %m",
+ return log_error_errno(r, "Failed to add new group \"%s\" to temporary group file: %m",
gr->gr_name);
group_changed = true;
@@ -726,19 +734,19 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
while (gr) {
r = putgrent_sane(gr, group);
if (r < 0)
- return log_debug_errno(r, "Failed to add existing group \"%s\" to temporary group file: %m",
+ return log_error_errno(r, "Failed to add existing group \"%s\" to temporary group file: %m",
gr->gr_name);
r = fgetgrent_sane(original, &gr);
if (r < 0)
- return log_debug_errno(r, "Failed to read %s: %m", group_path);
+ return log_error_errno(r, "Failed to read %s: %m", group_path);
if (r == 0)
break;
}
r = fflush_sync_and_check(group);
if (r < 0)
- return log_debug_errno(r, "Failed to flush %s: %m", group_tmp);
+ return log_error_errno(r, "Failed to flush %s: %m", group_tmp);
if (group_changed) {
*tmpfile = TAKE_PTR(group);
@@ -765,7 +773,7 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
r = fopen_temporary_label("/etc/gshadow", gshadow_path, &gshadow, &gshadow_tmp);
if (r < 0)
- return log_debug_errno(r, "Failed to open temporary copy of %s: %m", gshadow_path);
+ return log_error_errno(r, "Failed to open temporary copy of %s: %m", gshadow_path);
original = fopen(gshadow_path, "re");
if (original) {
@@ -773,7 +781,7 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
r = copy_rights_with_fallback(fileno(original), fileno(gshadow), gshadow_tmp);
if (r < 0)
- return log_debug_errno(r, "Failed to copy permissions from %s to %s: %m",
+ return log_error_errno(r, "Failed to copy permissions from %s to %s: %m",
gshadow_path, gshadow_tmp);
while ((r = fgetsgent_sane(original, &sg)) > 0) {
@@ -786,7 +794,7 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
r = putsgent_with_members(sg, gshadow);
if (r < 0)
- return log_debug_errno(r, "Failed to add existing group \"%s\" to temporary gshadow file: %m",
+ return log_error_errno(r, "Failed to add existing group \"%s\" to temporary gshadow file: %m",
sg->sg_namp);
if (r > 0)
group_changed = true;
@@ -796,9 +804,9 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
} else {
if (errno != ENOENT)
- return log_debug_errno(errno, "Failed to open %s: %m", gshadow_path);
+ return log_error_errno(errno, "Failed to open %s: %m", gshadow_path);
if (fchmod(fileno(gshadow), 0000) < 0)
- return log_debug_errno(errno, "Failed to fchmod %s: %m", gshadow_tmp);
+ return log_error_errno(errno, "Failed to fchmod %s: %m", gshadow_tmp);
}
ORDERED_HASHMAP_FOREACH(i, todo_gids) {
@@ -809,7 +817,7 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
r = putsgent_with_members(&n, gshadow);
if (r < 0)
- return log_debug_errno(r, "Failed to add new group \"%s\" to temporary gshadow file: %m",
+ return log_error_errno(r, "Failed to add new group \"%s\" to temporary gshadow file: %m",
n.sg_namp);
group_changed = true;
@@ -817,7 +825,7 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
r = fflush_sync_and_check(gshadow);
if (r < 0)
- return log_debug_errno(r, "Failed to flush %s: %m", gshadow_tmp);
+ return log_error_errno(r, "Failed to flush %s: %m", gshadow_tmp);
if (group_changed) {
*tmpfile = TAKE_PTR(gshadow);
@@ -858,30 +866,30 @@ static int write_files(void) {
if (group) {
r = make_backup("/etc/group", group_path);
if (r < 0)
- return log_debug_errno(r, "Failed to make backup %s: %m", group_path);
+ return log_error_errno(r, "Failed to make backup %s: %m", group_path);
}
if (gshadow) {
r = make_backup("/etc/gshadow", gshadow_path);
if (r < 0)
- return log_debug_errno(r, "Failed to make backup %s: %m", gshadow_path);
+ return log_error_errno(r, "Failed to make backup %s: %m", gshadow_path);
}
if (passwd) {
r = make_backup("/etc/passwd", passwd_path);
if (r < 0)
- return log_debug_errno(r, "Failed to make backup %s: %m", passwd_path);
+ return log_error_errno(r, "Failed to make backup %s: %m", passwd_path);
}
if (shadow) {
r = make_backup("/etc/shadow", shadow_path);
if (r < 0)
- return log_debug_errno(r, "Failed to make backup %s: %m", shadow_path);
+ return log_error_errno(r, "Failed to make backup %s: %m", shadow_path);
}
/* And make the new files count */
if (group) {
r = rename_and_apply_smack_floor_label(group_tmp, group_path);
if (r < 0)
- return log_debug_errno(r, "Failed to rename %s to %s: %m",
+ return log_error_errno(r, "Failed to rename %s to %s: %m",
group_tmp, group_path);
group_tmp = mfree(group_tmp);
@@ -891,7 +899,7 @@ static int write_files(void) {
if (gshadow) {
r = rename_and_apply_smack_floor_label(gshadow_tmp, gshadow_path);
if (r < 0)
- return log_debug_errno(r, "Failed to rename %s to %s: %m",
+ return log_error_errno(r, "Failed to rename %s to %s: %m",
gshadow_tmp, gshadow_path);
gshadow_tmp = mfree(gshadow_tmp);
@@ -900,7 +908,7 @@ static int write_files(void) {
if (passwd) {
r = rename_and_apply_smack_floor_label(passwd_tmp, passwd_path);
if (r < 0)
- return log_debug_errno(r, "Failed to rename %s to %s: %m",
+ return log_error_errno(r, "Failed to rename %s to %s: %m",
passwd_tmp, passwd_path);
passwd_tmp = mfree(passwd_tmp);
@@ -911,7 +919,7 @@ static int write_files(void) {
if (shadow) {
r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
if (r < 0)
- return log_debug_errno(r, "Failed to rename %s to %s: %m",
+ return log_error_errno(r, "Failed to rename %s to %s: %m",
shadow_tmp, shadow_path);
shadow_tmp = mfree(shadow_tmp);
@@ -981,7 +989,7 @@ static int root_stat(const char *p, struct stat *st) {
return RET_NERRNO(stat(fix, st));
}
-static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) {
+static int read_id_from_file(Item *i, uid_t *ret_uid, gid_t *ret_gid) {
struct stat st;
bool found_uid = false, found_gid = false;
uid_t uid = 0;
@@ -990,13 +998,13 @@ static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) {
assert(i);
/* First, try to get the GID directly */
- if (_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) {
+ if (ret_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) {
gid = st.st_gid;
found_gid = true;
}
/* Then, try to get the UID directly */
- if ((_uid || (_gid && !found_gid))
+ if ((ret_uid || (ret_gid && !found_gid))
&& i->uid_path
&& root_stat(i->uid_path, &st) >= 0) {
@@ -1004,14 +1012,14 @@ static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) {
found_uid = true;
/* If we need the gid, but had no success yet, also derive it from the UID path */
- if (_gid && !found_gid) {
+ if (ret_gid && !found_gid) {
gid = st.st_gid;
found_gid = true;
}
}
/* If that didn't work yet, then let's reuse the GID as UID */
- if (_uid && !found_uid && i->gid_path) {
+ if (ret_uid && !found_uid && i->gid_path) {
if (found_gid) {
uid = (uid_t) gid;
@@ -1022,18 +1030,18 @@ static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) {
}
}
- if (_uid) {
+ if (ret_uid) {
if (!found_uid)
return 0;
- *_uid = uid;
+ *ret_uid = uid;
}
- if (_gid) {
+ if (ret_gid) {
if (!found_gid)
return 0;
- *_gid = gid;
+ *ret_gid = gid;
}
return 1;
@@ -1444,7 +1452,9 @@ static int add_implicit(void) {
return 0;
}
-static bool item_equal(Item *a, Item *b) {
+static int item_equivalent(Item *a, Item *b) {
+ int r;
+
assert(a);
assert(b);
@@ -1454,6 +1464,7 @@ static bool item_equal(Item *a, Item *b) {
if (!streq_ptr(a->name, b->name))
return false;
+ /* Paths were simplified previously, so we can use streq. */
if (!streq_ptr(a->uid_path, b->uid_path))
return false;
@@ -1478,8 +1489,38 @@ static bool item_equal(Item *a, Item *b) {
if (!streq_ptr(a->home, b->home))
return false;
- if (!streq_ptr(a->shell, b->shell))
- return false;
+ /* Check if the two paths refer to the same file.
+ * If the paths are equal (after normalization), it's obviously the same file.
+ * If both paths specify a nologin shell, treat them as the same (e.g. /bin/true and /bin/false).
+ * Otherwise, try to resolve the paths, and see if we get the same result, (e.g. /sbin/nologin and
+ * /usr/sbin/nologin).
+ * If we can't resolve something, treat different paths as different. */
+
+ const char *a_shell = pick_shell(a),
+ *b_shell = pick_shell(b);
+ if (!path_equal_ptr(a_shell, b_shell) &&
+ !(is_nologin_shell(a_shell) && is_nologin_shell(b_shell))) {
+ _cleanup_free_ char *pa = NULL, *pb = NULL;
+
+ r = chase_symlinks(a_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pa, NULL);
+ if (r < 0) {
+ log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
+ r, "Failed to look up path '%s%s%s': %m",
+ strempty(arg_root), arg_root ? "/" : "", a_shell);
+ return ERRNO_IS_RESOURCE(r) ? r : false;
+ }
+
+ r = chase_symlinks(b_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pb, NULL);
+ if (r < 0) {
+ log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
+ r, "Failed to look up path '%s%s%s': %m",
+ strempty(arg_root), arg_root ? "/" : "", b_shell);
+ return ERRNO_IS_RESOURCE(r) ? r : false;
+ }
+
+ if (!path_equal(pa, pb))
+ return false;
+ }
return true;
}
@@ -1506,22 +1547,22 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
r = extract_many_words(&p, NULL, EXTRACT_UNQUOTE,
&action, &name, &id, &description, &home, &shell, NULL);
if (r < 0)
- return log_error_errno(r, "[%s:%u] Syntax error.", fname, line);
+ return log_syntax(NULL, LOG_ERR, fname, line, r, "Syntax error.");
if (r < 2)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Missing action and name columns.", fname, line);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Missing action and name columns.");
if (!isempty(p))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Trailing garbage.", fname, line);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Trailing garbage.");
/* Verify action */
if (strlen(action) != 1)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Unknown modifier '%s'", fname, line, action);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Unknown modifier '%s'.", action);
if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE))
- return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
- "[%s:%u] Unknown command type '%c'.", fname, line, action[0]);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
+ "Unknown command type '%c'.", action[0]);
/* Verify name */
if (empty_or_dash(name))
@@ -1530,12 +1571,11 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
if (name) {
r = specifier_printf(name, NAME_MAX, system_and_tmp_specifier_table, arg_root, NULL, &resolved_name);
if (r < 0)
- return log_error_errno(r, "[%s:%u] Failed to replace specifiers in '%s': %m", fname, line, name);
+ return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to replace specifiers in '%s': %m", 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);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "'%s' is not a valid user or group name.", resolved_name);
}
/* Verify id */
@@ -1545,8 +1585,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
if (id) {
r = specifier_printf(id, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_id);
if (r < 0)
- return log_error_errno(r, "[%s:%u] Failed to replace specifiers in '%s': %m",
- fname, line, name);
+ return log_syntax(NULL, LOG_ERR, fname, line, r,
+ "Failed to replace specifiers in '%s': %m", name);
}
/* Verify description */
@@ -1556,13 +1596,12 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
if (description) {
r = specifier_printf(description, LONG_LINE_MAX, system_and_tmp_specifier_table, arg_root, NULL, &resolved_description);
if (r < 0)
- return log_error_errno(r, "[%s:%u] Failed to replace specifiers in '%s': %m",
- fname, line, description);
+ return log_syntax(NULL, LOG_ERR, fname, line, r,
+ "Failed to replace specifiers in '%s': %m", description);
if (!valid_gecos(resolved_description))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] '%s' is not a valid GECOS field.",
- fname, line, resolved_description);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "'%s' is not a valid GECOS field.", resolved_description);
}
/* Verify home */
@@ -1572,13 +1611,14 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
if (home) {
r = specifier_printf(home, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_home);
if (r < 0)
- return log_error_errno(r, "[%s:%u] Failed to replace specifiers in '%s': %m",
- fname, line, home);
+ return log_syntax(NULL, LOG_ERR, fname, line, r,
+ "Failed to replace specifiers in '%s': %m", home);
+
+ path_simplify(resolved_home);
if (!valid_home(resolved_home))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] '%s' is not a valid home directory field.",
- fname, line, resolved_home);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "'%s' is not a valid home directory field.", resolved_home);
}
/* Verify shell */
@@ -1588,63 +1628,59 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
if (shell) {
r = specifier_printf(shell, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_shell);
if (r < 0)
- return log_error_errno(r, "[%s:%u] Failed to replace specifiers in '%s': %m",
- fname, line, shell);
+ return log_syntax(NULL, LOG_ERR, fname, line, r,
+ "Failed to replace specifiers in '%s': %m", shell);
+
+ path_simplify(resolved_shell);
if (!valid_shell(resolved_shell))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] '%s' is not a valid login shell field.",
- fname, line, resolved_shell);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "'%s' is not a valid login shell field.", resolved_shell);
}
switch (action[0]) {
case ADD_RANGE:
if (resolved_name)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Lines of type 'r' don't take a name field.",
- fname, line);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Lines of type 'r' don't take a name field.");
if (!resolved_id)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Lines of type 'r' require an ID range in the third field.",
- fname, line);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Lines of type 'r' require an ID range in the third field.");
if (description || home || shell)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Lines of type '%c' don't take a %s field.",
- fname, line, action[0],
- description ? "GECOS" : home ? "home directory" : "login shell");
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Lines of type '%c' don't take a %s field.",
+ action[0],
+ description ? "GECOS" : home ? "home directory" : "login shell");
r = uid_range_add_str(&uid_range, &n_uid_range, resolved_id);
if (r < 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Invalid UID range %s.", fname, line, resolved_id);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Invalid UID range %s.", resolved_id);
return 0;
case ADD_MEMBER: {
/* Try to extend an existing member or group item */
if (!name)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Lines of type 'm' require a user name in the second field.",
- fname, line);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Lines of type 'm' require a user name in the second field.");
if (!resolved_id)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Lines of type 'm' require a group name in the third field.",
- fname, line);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Lines of type 'm' require a group name in the third field.");
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);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "'%s' is not a valid user or group name.", resolved_id);
if (description || home || shell)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Lines of type '%c' don't take a %s field.",
- fname, line, action[0],
- description ? "GECOS" : home ? "home directory" : "login shell");
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Lines of type '%c' don't take a %s field.",
+ action[0],
+ description ? "GECOS" : home ? "home directory" : "login shell");
r = string_strv_ordered_hashmap_put(&members, resolved_id, resolved_name);
if (r < 0)
@@ -1655,9 +1691,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
case ADD_USER:
if (!name)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Lines of type 'u' require a user name in the second field.",
- fname, line);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Lines of type 'u' require a user name in the second field.");
r = ordered_hashmap_ensure_allocated(&users, &item_hash_ops);
if (r < 0)
@@ -1679,7 +1714,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
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);
+ return log_syntax(NULL, LOG_ERR, fname, line, r,
+ "Failed to parse GID: '%s': %m", id);
} else {
i->gid_set = true;
i->id_set_strict = true;
@@ -1689,7 +1725,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
if (!streq(resolved_id, "-")) {
r = parse_uid(resolved_id, &i->uid);
if (r < 0)
- return log_error_errno(r, "Failed to parse UID: '%s': %m", id);
+ return log_syntax(NULL, LOG_ERR, fname, line, r,
+ "Failed to parse UID: '%s': %m", id);
i->uid_set = true;
}
}
@@ -1704,15 +1741,14 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
case ADD_GROUP:
if (!name)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Lines of type 'g' require a user name in the second field.",
- fname, line);
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Lines of type 'g' require a user name in the second field.");
if (description || home || shell)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "[%s:%u] Lines of type '%c' don't take a %s field.",
- fname, line, action[0],
- description ? "GECOS" : home ? "home directory" : "login shell");
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
+ "Lines of type '%c' don't take a %s field.",
+ action[0],
+ description ? "GECOS" : home ? "home directory" : "login shell");
r = ordered_hashmap_ensure_allocated(&groups, &item_hash_ops);
if (r < 0)
@@ -1729,7 +1765,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
} else {
r = parse_gid(resolved_id, &i->gid);
if (r < 0)
- return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
+ return log_syntax(NULL, LOG_ERR, fname, line, r,
+ "Failed to parse GID: '%s': %m", id);
i->gid_set = true;
}
@@ -1747,11 +1784,14 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
existing = ordered_hashmap_get(h, i->name);
if (existing) {
- /* Two identical items are fine */
- if (!item_equal(existing, i))
- log_warning("%s:%u: conflict with earlier configuration for %s '%s', ignoring line.",
- fname, line,
- item_type_to_string(i->type), i->name);
+ /* Two functionally-equivalent items are fine */
+ r = item_equivalent(existing, i);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ log_syntax(NULL, LOG_WARNING, fname, line, SYNTHETIC_ERRNO(EUCLEAN),
+ "Conflict with earlier configuration for %s '%s', ignoring line.",
+ item_type_to_string(i->type), i->name);
return 0;
}
@@ -2146,11 +2186,7 @@ static int run(int argc, char *argv[]) {
ORDERED_HASHMAP_FOREACH(i, users)
(void) process_item(i);
- r = write_files();
- if (r < 0)
- return log_error_errno(r, "Failed to write files: %m");
-
- return 0;
+ return write_files();
}
DEFINE_MAIN_FUNCTION(run);
diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c
index 907de54eaa..48d9b1e0fb 100644
--- a/src/test/test-user-util.c
+++ b/src/test/test-user-util.c
@@ -347,8 +347,8 @@ static void test_get_user_creds_one(const char *id, const char *name, uid_t uid,
}
TEST(get_user_creds) {
- test_get_user_creds_one("root", "root", 0, 0, "/root", "/bin/sh");
- test_get_user_creds_one("0", "root", 0, 0, "/root", "/bin/sh");
+ test_get_user_creds_one("root", "root", 0, 0, "/root", DEFAULT_USER_SHELL);
+ test_get_user_creds_one("0", "root", 0, 0, "/root", DEFAULT_USER_SHELL);
test_get_user_creds_one(NOBODY_USER_NAME, NOBODY_USER_NAME, UID_NOBODY, GID_NOBODY, "/", NOLOGIN);
test_get_user_creds_one("65534", NOBODY_USER_NAME, UID_NOBODY, GID_NOBODY, "/", NOLOGIN);
}
diff --git a/test/test-execute/exec-specifier.service b/test/test-execute/exec-specifier.service
index ae8b2428bc..321d0e338a 100644
--- a/test/test-execute/exec-specifier.service
+++ b/test/test-execute/exec-specifier.service
@@ -26,7 +26,7 @@ ExecStart=sh -c 'test %U = $$(id -u)'
ExecStart=sh -c 'test %g = $$(id -gn)'
ExecStart=sh -c 'test %G = $$(id -g)'
ExecStart=test %h = /root
-ExecStart=sh -c 'test %s = /bin/sh'
+ExecStart=sh -c 'test -x %s'
ExecStart=sh -c 'test %m = $$(cat /etc/machine-id)'
ExecStart=sh -c 'test %b = $$(cat /proc/sys/kernel/random/boot_id | sed -e 's/-//g')'
ExecStart=sh -c 'test %H = $$(uname -n)'
diff --git a/test/test-execute/exec-specifier@.service b/test/test-execute/exec-specifier@.service
index 5e30efce4c..46c8503f1d 100644
--- a/test/test-execute/exec-specifier@.service
+++ b/test/test-execute/exec-specifier@.service
@@ -23,7 +23,7 @@ ExecStart=sh -c 'test %U = $$(id -u)'
ExecStart=sh -c 'test %g = $$(id -gn)'
ExecStart=sh -c 'test %G = $$(id -g)'
ExecStart=test %h = /root
-ExecStart=sh -c 'test %s = /bin/sh'
+ExecStart=sh -c 'test -x %s'
ExecStart=sh -c 'test %m = $$(cat /etc/machine-id)'
ExecStart=sh -c 'test %b = $$(cat /proc/sys/kernel/random/boot_id | sed -e 's/-//g')'
ExecStart=sh -c 'test %H = $$(uname -n)'
diff --git a/test/test-sysusers.sh.in b/test/test-sysusers.sh.in
index c9d9bd993b..950dc297d8 100755
--- a/test/test-sysusers.sh.in
+++ b/test/test-sysusers.sh.in
@@ -152,7 +152,7 @@ for f in $(ls -1 $SOURCE/unhappy-*.input | sort -V); do
echo "*** Running test $f"
prepare_testdir ${f%.input}
cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
- $SYSUSERS --root=$TESTDIR 2>&1 | tail -n1 > $TESTDIR/err
+ $SYSUSERS --root=$TESTDIR 2>&1 | tail -n1 | sed -r 's/^[^:]+:[^:]+://' >$TESTDIR/err
if ! diff -u $TESTDIR/err ${f%.*}.expected-err; then
echo "**** Unexpected error output for $f"
cat $TESTDIR/err
diff --git a/test/test-sysusers/unhappy-1.expected-err b/test/test-sysusers/unhappy-1.expected-err
index d3342402e9..f6b1b3c5e6 100644
--- a/test/test-sysusers/unhappy-1.expected-err
+++ b/test/test-sysusers/unhappy-1.expected-err
@@ -1 +1 @@
-Failed to parse UID: '9999999999': Numerical result out of range
+ Failed to parse UID: '9999999999': Numerical result out of range