1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2025-01-08 21:17:47 +03:00

Merge pull request #7973 from mvo5/sysusers-uid-gid

sysusers: allow uid:gid in sysusers.conf files
This commit is contained in:
Yu Watanabe 2018-01-27 17:24:39 +09:00 committed by GitHub
commit 786b8fa0fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 173 additions and 85 deletions

View File

@ -191,7 +191,10 @@ u root 0 "Superuser" /root</programlisting>
in the file system. In this case, the UID/GID is read from the
path's owner/group. This is useful to create users whose UID/GID
match the owners of pre-existing files (such as SUID or SGID
binaries).</para>
binaries).
The syntax <literal><replaceable>uid</replaceable>:<replaceable>gid</replaceable></literal> is also supported to
allow creating user and group pairs with different numeric UID and GID values. The group with the indicated GID must get created explicitly before or it must already exist.
</para>
<para>For <varname>m</varname> lines, this field should contain
the group name to add to a user to.</para>

View File

@ -64,6 +64,7 @@ typedef struct Item {
uid_t uid;
bool gid_set:1;
bool gid_must_exist:1;
bool uid_set:1;
bool todo_user:1;
@ -74,9 +75,9 @@ static char *arg_root = NULL;
static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysusers.d");
static Hashmap *users = NULL, *groups = NULL;
static Hashmap *todo_uids = NULL, *todo_gids = NULL;
static Hashmap *members = NULL;
static OrderedHashmap *users = NULL, *groups = NULL;
static OrderedHashmap *todo_uids = NULL, *todo_gids = NULL;
static OrderedHashmap *members = NULL;
static Hashmap *database_uid = NULL, *database_user = NULL;
static Hashmap *database_gid = NULL, *database_group = NULL;
@ -256,7 +257,7 @@ static int putgrent_with_members(const struct group *gr, FILE *group) {
assert(gr);
assert(group);
a = hashmap_get(members, gr->gr_name);
a = ordered_hashmap_get(members, gr->gr_name);
if (a) {
_cleanup_strv_free_ char **l = NULL;
bool added = false;
@ -307,7 +308,7 @@ static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) {
assert(sg);
assert(gshadow);
a = hashmap_get(members, sg->sg_namp);
a = ordered_hashmap_get(members, sg->sg_namp);
if (a) {
_cleanup_strv_free_ char **l = NULL;
bool added = false;
@ -387,7 +388,7 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
Item *i;
int r;
if (hashmap_size(todo_uids) == 0)
if (ordered_hashmap_size(todo_uids) == 0)
return 0;
r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
@ -405,13 +406,13 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
errno = 0;
while ((pw = fgetpwent(original))) {
i = hashmap_get(users, pw->pw_name);
i = ordered_hashmap_get(users, pw->pw_name);
if (i && i->todo_user) {
log_error("%s: User \"%s\" already exists.", passwd_path, pw->pw_name);
return -EEXIST;
}
if (hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid))) {
if (ordered_hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid))) {
log_error("%s: Detected collision for UID " UID_FMT ".", passwd_path, pw->pw_uid);
return -EEXIST;
}
@ -432,7 +433,7 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
return -errno;
}
HASHMAP_FOREACH(i, todo_uids, iterator) {
ORDERED_HASHMAP_FOREACH(i, todo_uids, iterator) {
struct passwd n = {
.pw_name = i->name,
.pw_uid = i->uid,
@ -474,7 +475,7 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
Item *i;
int r;
if (hashmap_size(todo_uids) == 0)
if (ordered_hashmap_size(todo_uids) == 0)
return 0;
r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp);
@ -494,7 +495,7 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
errno = 0;
while ((sp = fgetspent(original))) {
i = hashmap_get(users, sp->sp_namp);
i = ordered_hashmap_get(users, sp->sp_namp);
if (i && i->todo_user) {
/* we will update the existing entry */
sp->sp_lstchg = lstchg;
@ -502,7 +503,7 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
/* only the /etc/shadow stage is left, so we can
* safely remove the item from the todo set */
i->todo_user = false;
hashmap_remove(todo_uids, UID_TO_PTR(i->uid));
ordered_hashmap_remove(todo_uids, UID_TO_PTR(i->uid));
}
errno = 0;
@ -521,7 +522,7 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
return -errno;
}
HASHMAP_FOREACH(i, todo_uids, iterator) {
ORDERED_HASHMAP_FOREACH(i, todo_uids, iterator) {
struct spwd n = {
.sp_namp = i->name,
.sp_pwdp = (char*) "!!",
@ -558,7 +559,7 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
Item *i;
int r;
if (hashmap_size(todo_gids) == 0 && hashmap_size(members) == 0)
if (ordered_hashmap_size(todo_gids) == 0 && ordered_hashmap_size(members) == 0)
return 0;
r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp);
@ -580,13 +581,13 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
* entries anyway here, let's make an extra verification
* step that we don't generate duplicate entries. */
i = hashmap_get(groups, gr->gr_name);
i = ordered_hashmap_get(groups, gr->gr_name);
if (i && i->todo_group) {
log_error("%s: Group \"%s\" already exists.", group_path, gr->gr_name);
return -EEXIST;
}
if (hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid))) {
if (ordered_hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid))) {
log_error("%s: Detected collision for GID " GID_FMT ".", group_path, gr->gr_gid);
return -EEXIST;
}
@ -609,7 +610,7 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
return -errno;
}
HASHMAP_FOREACH(i, todo_gids, iterator) {
ORDERED_HASHMAP_FOREACH(i, todo_gids, iterator) {
struct group n = {
.gr_name = i->name,
.gr_gid = i->gid,
@ -645,7 +646,7 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
Item *i;
int r;
if (hashmap_size(todo_gids) == 0 && hashmap_size(members) == 0)
if (ordered_hashmap_size(todo_gids) == 0 && ordered_hashmap_size(members) == 0)
return 0;
r = fopen_temporary_label("/etc/gshadow", gshadow_path, &gshadow, &gshadow_tmp);
@ -663,7 +664,7 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
errno = 0;
while ((sg = fgetsgent(original))) {
i = hashmap_get(groups, sg->sg_namp);
i = ordered_hashmap_get(groups, sg->sg_namp);
if (i && i->todo_group) {
log_error("%s: Group \"%s\" already exists.", gshadow_path, sg->sg_namp);
return -EEXIST;
@ -687,7 +688,7 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
return -errno;
}
HASHMAP_FOREACH(i, todo_gids, iterator) {
ORDERED_HASHMAP_FOREACH(i, todo_gids, iterator) {
struct sgrp n = {
.sg_namp = i->name,
.sg_passwd = (char*) "!!",
@ -807,12 +808,12 @@ static int uid_is_ok(uid_t uid, const char *name) {
Item *i;
/* Let's see if we already have assigned the UID a second time */
if (hashmap_get(todo_uids, UID_TO_PTR(uid)))
if (ordered_hashmap_get(todo_uids, UID_TO_PTR(uid)))
return 0;
/* Try to avoid using uids that are already used by a group
* that doesn't have the same name as our new user. */
i = hashmap_get(todo_gids, GID_TO_PTR(uid));
i = ordered_hashmap_get(todo_gids, GID_TO_PTR(uid));
if (i && !streq(i->name, name))
return 0;
@ -1012,11 +1013,11 @@ static int add_user(Item *i) {
i->uid = search_uid;
}
r = hashmap_ensure_allocated(&todo_uids, NULL);
r = ordered_hashmap_ensure_allocated(&todo_uids, NULL);
if (r < 0)
return log_oom();
r = hashmap_put(todo_uids, UID_TO_PTR(i->uid), i);
r = ordered_hashmap_put(todo_uids, UID_TO_PTR(i->uid), i);
if (r < 0)
return log_oom();
@ -1030,11 +1031,11 @@ static int gid_is_ok(gid_t gid) {
struct group *g;
struct passwd *p;
if (hashmap_get(todo_gids, GID_TO_PTR(gid)))
if (ordered_hashmap_get(todo_gids, GID_TO_PTR(gid)))
return 0;
/* Avoid reusing gids that are already used by a different user */
if (hashmap_get(todo_uids, UID_TO_PTR(gid)))
if (ordered_hashmap_get(todo_uids, UID_TO_PTR(gid)))
return 0;
if (hashmap_contains(database_gid, GID_TO_PTR(gid)))
@ -1098,6 +1099,18 @@ static int add_group(Item *i) {
r = gid_is_ok(i->gid);
if (r < 0)
return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
if (i->gid_must_exist) {
/* If we require the gid to already exist we can return here:
* r > 0: means the gid does not exist -> fail
* r == 0: means the gid exists -> nothing more to do.
*/
if (r > 0) {
log_error("Failed to create %s: please create GID %d", i->name, i->gid);
return -EINVAL;
}
if (r == 0)
return 0;
}
if (r == 0) {
log_debug("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name);
i->gid_set = false;
@ -1157,11 +1170,11 @@ static int add_group(Item *i) {
i->gid = search_uid;
}
r = hashmap_ensure_allocated(&todo_gids, NULL);
r = ordered_hashmap_ensure_allocated(&todo_gids, NULL);
if (r < 0)
return log_oom();
r = hashmap_put(todo_gids, GID_TO_PTR(i->gid), i);
r = ordered_hashmap_put(todo_gids, GID_TO_PTR(i->gid), i);
if (r < 0)
return log_oom();
@ -1185,30 +1198,8 @@ static int process_item(Item *i) {
return add_user(i);
case ADD_GROUP: {
Item *j;
j = hashmap_get(users, i->name);
if (j) {
/* There's already user to be created for this
* name, let's process that in one step */
if (i->gid_set) {
j->gid = i->gid;
j->gid_set = true;
}
if (i->gid_path) {
r = free_and_strdup(&j->gid_path, i->gid_path);
if (r < 0)
return log_oom();
}
return 0;
}
case ADD_GROUP:
return add_group(i);
}
default:
assert_not_reached("Unknown item type");
@ -1237,15 +1228,15 @@ static int add_implicit(void) {
/* Implicitly create additional users and groups, if they were listed in "m" lines */
HASHMAP_FOREACH_KEY(l, g, members, iterator) {
ORDERED_HASHMAP_FOREACH_KEY(l, g, members, iterator) {
Item *i;
char **m;
i = hashmap_get(groups, g);
i = ordered_hashmap_get(groups, g);
if (!i) {
_cleanup_(item_freep) Item *j = NULL;
r = hashmap_ensure_allocated(&groups, &string_hash_ops);
r = ordered_hashmap_ensure_allocated(&groups, &string_hash_ops);
if (r < 0)
return log_oom();
@ -1258,7 +1249,7 @@ static int add_implicit(void) {
if (!j->name)
return log_oom();
r = hashmap_put(groups, j->name, j);
r = ordered_hashmap_put(groups, j->name, j);
if (r < 0)
return log_oom();
@ -1268,11 +1259,11 @@ static int add_implicit(void) {
STRV_FOREACH(m, l) {
i = hashmap_get(users, *m);
i = ordered_hashmap_get(users, *m);
if (!i) {
_cleanup_(item_freep) Item *j = NULL;
r = hashmap_ensure_allocated(&users, &string_hash_ops);
r = ordered_hashmap_ensure_allocated(&users, &string_hash_ops);
if (r < 0)
return log_oom();
@ -1285,7 +1276,7 @@ static int add_implicit(void) {
if (!j->name)
return log_oom();
r = hashmap_put(users, j->name, j);
r = ordered_hashmap_put(users, j->name, j);
if (r < 0)
return log_oom();
@ -1348,7 +1339,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
_cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL, *resolved_id = NULL, *description = NULL, *home = NULL;
_cleanup_(item_freep) Item *i = NULL;
Item *existing;
Hashmap *h;
OrderedHashmap *h;
int r;
const char *p;
@ -1494,11 +1485,11 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
r = hashmap_ensure_allocated(&members, &string_hash_ops);
r = ordered_hashmap_ensure_allocated(&members, &string_hash_ops);
if (r < 0)
return log_oom();
l = hashmap_get(members, resolved_id);
l = ordered_hashmap_get(members, resolved_id);
if (l) {
/* A list for this group name already exists, let's append to it */
r = strv_push(&l, resolved_name);
@ -1507,7 +1498,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
resolved_name = NULL;
assert_se(hashmap_update(members, resolved_id, l) >= 0);
assert_se(ordered_hashmap_update(members, resolved_id, l) >= 0);
} else {
/* No list for this group name exists yet, create one */
@ -1518,7 +1509,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
l[0] = resolved_name;
l[1] = NULL;
r = hashmap_put(members, resolved_id, l);
r = ordered_hashmap_put(members, resolved_id, l);
if (r < 0) {
free(l);
return log_oom();
@ -1536,7 +1527,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
r = hashmap_ensure_allocated(&users, &string_hash_ops);
r = ordered_hashmap_ensure_allocated(&users, &string_hash_ops);
if (r < 0)
return log_oom();
@ -1551,11 +1542,18 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
path_kill_slashes(i->uid_path);
} else {
r = parse_uid(resolved_id, &i->uid);
if (r < 0) {
log_error("Failed to parse UID: %s", id);
return -EBADMSG;
_cleanup_free_ char *uid = NULL, *gid = NULL;
if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
r = parse_gid(gid, &i->gid);
if (r < 0)
return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
i->gid_set = true;
i->gid_must_exist = true;
free_and_replace(resolved_id, uid);
}
r = parse_uid(resolved_id, &i->uid);
if (r < 0)
return log_error_errno(r, "Failed to parse UID: '%s': %m", id);
i->uid_set = true;
}
@ -1586,7 +1584,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
r = hashmap_ensure_allocated(&groups, &string_hash_ops);
r = ordered_hashmap_ensure_allocated(&groups, &string_hash_ops);
if (r < 0)
return log_oom();
@ -1602,10 +1600,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
path_kill_slashes(i->gid_path);
} else {
r = parse_gid(resolved_id, &i->gid);
if (r < 0) {
log_error("Failed to parse GID: %s", id);
return -EBADMSG;
}
if (r < 0)
return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
i->gid_set = true;
}
@ -1622,7 +1618,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
i->name = resolved_name;
resolved_name = NULL;
existing = hashmap_get(h, i->name);
existing = ordered_hashmap_get(h, i->name);
if (existing) {
/* Two identical items are fine */
@ -1632,7 +1628,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return 0;
}
r = hashmap_put(h, i->name, i);
r = ordered_hashmap_put(h, i->name, i);
if (r < 0)
return log_oom();
@ -1853,10 +1849,10 @@ int main(int argc, char *argv[]) {
goto finish;
}
HASHMAP_FOREACH(i, groups, iterator)
ORDERED_HASHMAP_FOREACH(i, groups, iterator)
process_item(i);
HASHMAP_FOREACH(i, users, iterator)
ORDERED_HASHMAP_FOREACH(i, users, iterator)
process_item(i);
r = write_files();
@ -1864,17 +1860,17 @@ int main(int argc, char *argv[]) {
log_error_errno(r, "Failed to write files: %m");
finish:
hashmap_free_with_destructor(groups, item_free);
hashmap_free_with_destructor(users, item_free);
ordered_hashmap_free_with_destructor(groups, item_free);
ordered_hashmap_free_with_destructor(users, item_free);
while ((n = hashmap_first_key(members))) {
strv_free(hashmap_steal_first(members));
while ((n = ordered_hashmap_first_key(members))) {
strv_free(ordered_hashmap_steal_first(members));
free(n);
}
hashmap_free(members);
ordered_hashmap_free(members);
hashmap_free(todo_uids);
hashmap_free(todo_gids);
ordered_hashmap_free(todo_uids);
ordered_hashmap_free(todo_gids);
free_database(database_user, database_uid);
free_database(database_group, database_gid);

View File

@ -0,0 +1,4 @@
BUILD_DIR=$(shell ../../tools/find-build-dir.sh)
all setup clean run:
@basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@

View File

@ -0,0 +1,2 @@
g1:x:111:
u1:x:222:

View File

@ -0,0 +1 @@
u1:x:222:222::/:/sbin/nologin

View File

@ -0,0 +1,3 @@
#Type Name ID GECOS HOMEDIR
u u1 222 - -
g g1 111 - -

View File

@ -0,0 +1 @@
u1:x:999:

View File

@ -0,0 +1 @@
u1:x:999:999:some gecos:/random/dir:/sbin/nologin

View File

@ -0,0 +1,2 @@
#Type Name ID GECOS HOMEDIR
u u1 - "some gecos" /random/dir

View File

@ -0,0 +1,4 @@
hoge:x:300:
baz:x:302:
foo:x:301:
ccc:x:305:

View File

@ -0,0 +1,4 @@
foo:x:301:301::/:/sbin/nologin
aaa:x:303:302::/:/sbin/nologin
bbb:x:304:302::/:/sbin/nologin
ccc:x:305:305::/:/sbin/nologin

View File

@ -0,0 +1,7 @@
g hoge 300 - -
u foo 301 - -
g baz 302 - -
u aaa 303:302 - -
u bbb 304:302 - -
u ccc 305 - -

View File

@ -0,0 +1 @@
xxx:x:310:

View File

@ -0,0 +1,2 @@
yyy:x:311:310::/:/sbin/nologin
xxx:x:312:310::/:/sbin/nologin

View File

@ -0,0 +1,3 @@
g xxx 310
u yyy 311:310
u xxx 312:310

49
test/TEST-21-SYSUSERS/test.sh Executable file
View File

@ -0,0 +1,49 @@
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh
set -e
TEST_DESCRIPTION="Sysuser-related tests"
. $TEST_BASE_DIR/test-functions
test_setup() {
mkdir -p $TESTDIR/etc $TESTDIR/usr/lib/sysusers.d $TESTDIR/tmp
}
test_run() {
# ensure our build of systemd-sysusers is run
PATH=${BUILD_DIR}:$PATH
# happy tests
for f in test-*.input; do
echo "*** Running $f"
rm -f $TESTDIR/etc/*
cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
systemd-sysusers --root=$TESTDIR
if ! diff -u $TESTDIR/etc/passwd ${f%.*}.expected-passwd; then
echo "**** Unexpected output for $f"
exit 1
fi
if ! diff -u $TESTDIR/etc/group ${f%.*}.expected-group; then
echo "**** Unexpected output for $f"
exit 1
fi
done
# tests for error conditions
for f in unhappy-*.input; do
echo "*** Running test $f"
rm -f $TESTDIR/etc/*
cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
systemd-sysusers --root=$TESTDIR 2> /dev/null
journalctl -t systemd-sysusers -o cat | tail -n1 > $TESTDIR/tmp/err
if ! diff -u $TESTDIR/tmp/err ${f%.*}.expected-err; then
echo "**** Unexpected error output for $f"
cat $TESTDIR/tmp/err
exit 1
fi
done
}
do_test "$@"

View File

@ -0,0 +1 @@
Failed to parse UID: '9999999999': Numerical result out of range

View File

@ -0,0 +1 @@
u u1 9999999999 - -

View File

@ -0,0 +1 @@
Failed to create u1: please create GID 100

View File

@ -0,0 +1,2 @@
# it is not allowed to create groups implicitely in the uid:gid syntax
u u1 100:100 -