mirror of
https://github.com/systemd/systemd
synced 2024-09-06 16:56:43 +00:00
sysusers: support creating users with a specific primary group
This extends the "uid:gid" syntax for "u" lines so that a group name can be given instead of a GID. This requires that the group is either queued for creation by sysusers, or it is already defined on the system. Closes #14340
This commit is contained in:
parent
4e3132d6d6
commit
649916d356
|
@ -101,8 +101,8 @@ u root 0 "Superuser" /root /bin/zsh</pro
|
||||||
<term><varname>u</varname></term>
|
<term><varname>u</varname></term>
|
||||||
<listitem><para>Create a system user and group of the specified name should
|
<listitem><para>Create a system user and group of the specified name should
|
||||||
they not exist yet. The user's primary group will be set to the group
|
they not exist yet. The user's primary group will be set to the group
|
||||||
bearing the same name. The account will be created disabled, so that logins
|
bearing the same name unless the ID field specifies it. The account will be
|
||||||
are not allowed.</para></listitem>
|
created disabled, so that logins are not allowed.</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
|
@ -166,9 +166,10 @@ u root 0 "Superuser" /root /bin/zsh</pro
|
||||||
path's owner/group. This is useful to create users whose UID/GID
|
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
|
match the owners of pre-existing files (such as SUID or SGID
|
||||||
binaries).
|
binaries).
|
||||||
The syntax <literal><replaceable>uid</replaceable>:<replaceable>gid</replaceable></literal> is also supported to
|
The syntaxes <literal><replaceable>uid</replaceable>:<replaceable>gid</replaceable></literal> and
|
||||||
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. Specifying <literal>-</literal> for the UID in this syntax
|
<literal><replaceable>uid</replaceable>:<replaceable>groupname</replaceable></literal> are supported to
|
||||||
is also supported.
|
allow creating users with specific primary groups. The given group must be created explicitly, or it
|
||||||
|
must already exist. Specifying <literal>-</literal> for the UID in these syntaxes is also supported.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>For <varname>m</varname> lines, this field should contain
|
<para>For <varname>m</varname> lines, this field should contain
|
||||||
|
|
|
@ -39,6 +39,7 @@ typedef struct Item {
|
||||||
ItemType type;
|
ItemType type;
|
||||||
|
|
||||||
char *name;
|
char *name;
|
||||||
|
char *group_name;
|
||||||
char *uid_path;
|
char *uid_path;
|
||||||
char *gid_path;
|
char *gid_path;
|
||||||
char *description;
|
char *description;
|
||||||
|
@ -1085,18 +1086,15 @@ static int gid_is_ok(gid_t gid) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int add_group(Item *i) {
|
static int get_gid_by_name(const char *name, gid_t *gid) {
|
||||||
void *z;
|
void *z;
|
||||||
int r;
|
|
||||||
|
|
||||||
assert(i);
|
assert(gid);
|
||||||
|
|
||||||
/* Check the database directly */
|
/* Check the database directly */
|
||||||
z = hashmap_get(database_by_groupname, i->name);
|
z = hashmap_get(database_by_groupname, name);
|
||||||
if (z) {
|
if (z) {
|
||||||
log_debug("Group %s already exists.", i->name);
|
*gid = PTR_TO_GID(z);
|
||||||
i->gid = PTR_TO_GID(z);
|
|
||||||
i->gid_set = true;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1105,15 +1103,30 @@ static int add_group(Item *i) {
|
||||||
struct group *g;
|
struct group *g;
|
||||||
|
|
||||||
errno = 0;
|
errno = 0;
|
||||||
g = getgrnam(i->name);
|
g = getgrnam(name);
|
||||||
if (g) {
|
if (g) {
|
||||||
log_debug("Group %s already exists.", i->name);
|
*gid = g->gr_gid;
|
||||||
i->gid = g->gr_gid;
|
|
||||||
i->gid_set = true;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!IN_SET(errno, 0, ENOENT))
|
if (!IN_SET(errno, 0, ENOENT))
|
||||||
return log_error_errno(errno, "Failed to check if group %s already exists: %m", i->name);
|
return log_error_errno(errno, "Failed to check if group %s already exists: %m", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int add_group(Item *i) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(i);
|
||||||
|
|
||||||
|
r = get_gid_by_name(i->name, &i->gid);
|
||||||
|
if (r != -ENOENT) {
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
log_debug("Group %s already exists.", i->name);
|
||||||
|
i->gid_set = true;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Try to use the suggested numeric gid */
|
/* Try to use the suggested numeric gid */
|
||||||
|
@ -1214,14 +1227,22 @@ static int process_item(Item *i) {
|
||||||
case ADD_USER: {
|
case ADD_USER: {
|
||||||
Item *j;
|
Item *j;
|
||||||
|
|
||||||
j = ordered_hashmap_get(groups, i->name);
|
j = ordered_hashmap_get(groups, i->group_name ?: i->name);
|
||||||
if (j && j->todo_group) {
|
if (j && j->todo_group) {
|
||||||
/* When the group with the same name is already in queue,
|
/* When a group with the target name is already in queue,
|
||||||
* use the information about the group and do not create
|
* use the information about the group and do not create
|
||||||
* duplicated group entry. */
|
* duplicated group entry. */
|
||||||
i->gid_set = j->gid_set;
|
i->gid_set = j->gid_set;
|
||||||
i->gid = j->gid;
|
i->gid = j->gid;
|
||||||
i->id_set_strict = true;
|
i->id_set_strict = true;
|
||||||
|
} else if (i->group_name) {
|
||||||
|
/* When a group name was given instead of a GID and it's
|
||||||
|
* not in queue, then it must already exist. */
|
||||||
|
r = get_gid_by_name(i->group_name, &i->gid);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Group %s not found.", i->group_name);
|
||||||
|
i->gid_set = true;
|
||||||
|
i->id_set_strict = true;
|
||||||
} else {
|
} else {
|
||||||
r = add_group(i);
|
r = add_group(i);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
|
@ -1244,6 +1265,7 @@ static Item* item_free(Item *i) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
free(i->name);
|
free(i->name);
|
||||||
|
free(i->group_name);
|
||||||
free(i->uid_path);
|
free(i->uid_path);
|
||||||
free(i->gid_path);
|
free(i->gid_path);
|
||||||
free(i->description);
|
free(i->description);
|
||||||
|
@ -1560,10 +1582,15 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
|
||||||
_cleanup_free_ char *uid = NULL, *gid = NULL;
|
_cleanup_free_ char *uid = NULL, *gid = NULL;
|
||||||
if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
|
if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
|
||||||
r = parse_gid(gid, &i->gid);
|
r = parse_gid(gid, &i->gid);
|
||||||
if (r < 0)
|
if (r < 0) {
|
||||||
return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
|
if (valid_user_group_name(gid))
|
||||||
i->gid_set = true;
|
i->group_name = TAKE_PTR(gid);
|
||||||
i->id_set_strict = true;
|
else
|
||||||
|
return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
|
||||||
|
} else {
|
||||||
|
i->gid_set = true;
|
||||||
|
i->id_set_strict = true;
|
||||||
|
}
|
||||||
free_and_replace(resolved_id, uid);
|
free_and_replace(resolved_id, uid);
|
||||||
}
|
}
|
||||||
if (!streq(resolved_id, "-")) {
|
if (!streq(resolved_id, "-")) {
|
||||||
|
|
5
test/TEST-21-SYSUSERS/test-13.expected-group
Normal file
5
test/TEST-21-SYSUSERS/test-13.expected-group
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
hoge:x:300:
|
||||||
|
baz:x:302:
|
||||||
|
yyy:x:SYSTEM_GID_MAX:
|
||||||
|
foo:x:301:
|
||||||
|
ccc:x:305:
|
5
test/TEST-21-SYSUSERS/test-13.expected-passwd
Normal file
5
test/TEST-21-SYSUSERS/test-13.expected-passwd
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
foo:x:301:301::/:NOLOGIN
|
||||||
|
aaa:x:303:302::/:NOLOGIN
|
||||||
|
bbb:x:304:302::/:NOLOGIN
|
||||||
|
ccc:x:305:305::/:NOLOGIN
|
||||||
|
zzz:x:306:SYSTEM_GID_MAX::/:NOLOGIN
|
13
test/TEST-21-SYSUSERS/test-13.input
Normal file
13
test/TEST-21-SYSUSERS/test-13.input
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Ensure that the semantic for the uid:groupname syntax is correct
|
||||||
|
#
|
||||||
|
#Type Name ID GECOS HOMEDIR
|
||||||
|
g hoge 300 - -
|
||||||
|
u foo 301 - -
|
||||||
|
|
||||||
|
g baz 302 - -
|
||||||
|
u aaa 303:baz - -
|
||||||
|
u bbb 304:baz - -
|
||||||
|
u ccc 305 - -
|
||||||
|
|
||||||
|
g yyy -
|
||||||
|
u zzz 306:yyy
|
1
test/TEST-21-SYSUSERS/test-14.expected-group
Normal file
1
test/TEST-21-SYSUSERS/test-14.expected-group
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pre:x:987:
|
1
test/TEST-21-SYSUSERS/test-14.expected-passwd
Normal file
1
test/TEST-21-SYSUSERS/test-14.expected-passwd
Normal file
|
@ -0,0 +1 @@
|
||||||
|
aaa:x:SYSTEM_UID_MAX:987::/:NOLOGIN
|
1
test/TEST-21-SYSUSERS/test-14.initial-group
Normal file
1
test/TEST-21-SYSUSERS/test-14.initial-group
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pre:x:987:
|
4
test/TEST-21-SYSUSERS/test-14.input
Normal file
4
test/TEST-21-SYSUSERS/test-14.input
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Ensure that a preexisting system group can be used as primary
|
||||||
|
#
|
||||||
|
#Type Name ID GECOS HOMEDIR
|
||||||
|
u aaa -:pre
|
|
@ -23,6 +23,7 @@ preprocess() {
|
||||||
# get this value from config.h, however the autopkgtest fails with
|
# get this value from config.h, however the autopkgtest fails with
|
||||||
# it
|
# it
|
||||||
SYSTEM_UID_MAX=$(awk 'BEGIN { uid=999 } /^\s*SYS_UID_MAX\s+/ { uid=$2 } END { print uid }' /etc/login.defs)
|
SYSTEM_UID_MAX=$(awk 'BEGIN { uid=999 } /^\s*SYS_UID_MAX\s+/ { uid=$2 } END { print uid }' /etc/login.defs)
|
||||||
|
SYSTEM_GID_MAX=$(awk 'BEGIN { gid=999 } /^\s*SYS_GID_MAX\s+/ { gid=$2 } END { print gid }' /etc/login.defs)
|
||||||
|
|
||||||
# we can't rely on config.h to get the nologin path, as autopkgtest
|
# we can't rely on config.h to get the nologin path, as autopkgtest
|
||||||
# uses pre-compiled binaries, so extract it from the systemd-sysusers
|
# uses pre-compiled binaries, so extract it from the systemd-sysusers
|
||||||
|
@ -30,6 +31,7 @@ preprocess() {
|
||||||
NOLOGIN=$(strings $(type -p systemd-sysusers) | grep nologin)
|
NOLOGIN=$(strings $(type -p systemd-sysusers) | grep nologin)
|
||||||
|
|
||||||
sed -e "s/SYSTEM_UID_MAX/${SYSTEM_UID_MAX}/g" \
|
sed -e "s/SYSTEM_UID_MAX/${SYSTEM_UID_MAX}/g" \
|
||||||
|
-e "s/SYSTEM_GID_MAX/${SYSTEM_GID_MAX}/g" \
|
||||||
-e "s#NOLOGIN#${NOLOGIN}#g" "$in"
|
-e "s#NOLOGIN#${NOLOGIN}#g" "$in"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
test/TEST-21-SYSUSERS/unhappy-3.expected-err
Normal file
1
test/TEST-21-SYSUSERS/unhappy-3.expected-err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Group g1 not found.
|
4
test/TEST-21-SYSUSERS/unhappy-3.input
Normal file
4
test/TEST-21-SYSUSERS/unhappy-3.input
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Ensure it is not allowed to create groups implicitly in the uid:groupname syntax
|
||||||
|
#
|
||||||
|
#Type Name ID GECOS HOMEDIR
|
||||||
|
u u1 100:g1 -
|
Loading…
Reference in a new issue