]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysusers: support creating users with a specific primary group
authorDavid Michael <fedora.dm0@gmail.com>
Thu, 30 Jan 2020 19:04:14 +0000 (14:04 -0500)
committerLennart Poettering <lennart@poettering.net>
Sun, 2 Feb 2020 15:53:22 +0000 (16:53 +0100)
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

12 files changed:
man/sysusers.d.xml
src/sysusers/sysusers.c
test/TEST-21-SYSUSERS/test-13.expected-group [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-13.expected-passwd [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-13.input [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-14.expected-group [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-14.expected-passwd [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-14.initial-group [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-14.input [new file with mode: 0644]
test/TEST-21-SYSUSERS/test.sh
test/TEST-21-SYSUSERS/unhappy-3.expected-err [new file with mode: 0644]
test/TEST-21-SYSUSERS/unhappy-3.input [new file with mode: 0644]

index 2e93715be6fc11b03ceaed8fe414062af8bac8aa..72d8f623991131e8d71730471bd014569ceb7759 100644 (file)
@@ -101,8 +101,8 @@ u     root     0              "Superuser"           /root          /bin/zsh</pro
           <term><varname>u</varname></term>
           <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
-          bearing the same name. The account will be created disabled, so that logins
-          are not allowed.</para></listitem>
+          bearing the same name unless the ID field specifies it. The account will be
+          created disabled, so that logins are not allowed.</para></listitem>
         </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
       match the owners of pre-existing files (such as SUID or SGID
       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. Specifying <literal>-</literal> for the UID in this syntax
-      is also supported.
+      The syntaxes <literal><replaceable>uid</replaceable>:<replaceable>gid</replaceable></literal> and
+      <literal><replaceable>uid</replaceable>:<replaceable>groupname</replaceable></literal> are supported to
+      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>For <varname>m</varname> lines, this field should contain
index 08a2df707bedf8b813da846385f60c6b294804b8..2771fd959fbfd677dfe0f26b20a53df52996af1b 100644 (file)
@@ -39,6 +39,7 @@ typedef struct Item {
         ItemType type;
 
         char *name;
+        char *group_name;
         char *uid_path;
         char *gid_path;
         char *description;
@@ -1085,18 +1086,15 @@ static int gid_is_ok(gid_t gid) {
         return 1;
 }
 
-static int add_group(Item *i) {
+static int get_gid_by_name(const char *name, gid_t *gid) {
         void *z;
-        int r;
 
-        assert(i);
+        assert(gid);
 
         /* Check the database directly */
-        z = hashmap_get(database_by_groupname, i->name);
+        z = hashmap_get(database_by_groupname, name);
         if (z) {
-                log_debug("Group %s already exists.", i->name);
-                i->gid = PTR_TO_GID(z);
-                i->gid_set = true;
+                *gid = PTR_TO_GID(z);
                 return 0;
         }
 
@@ -1105,15 +1103,30 @@ static int add_group(Item *i) {
                 struct group *g;
 
                 errno = 0;
-                g = getgrnam(i->name);
+                g = getgrnam(name);
                 if (g) {
-                        log_debug("Group %s already exists.", i->name);
-                        i->gid = g->gr_gid;
-                        i->gid_set = true;
+                        *gid = g->gr_gid;
                         return 0;
                 }
                 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 */
@@ -1214,14 +1227,22 @@ static int process_item(Item *i) {
         case ADD_USER: {
                 Item *j;
 
-                j = ordered_hashmap_get(groups, i->name);
+                j = ordered_hashmap_get(groups, i->group_name ?: i->name);
                 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
                          * duplicated group entry. */
                         i->gid_set = j->gid_set;
                         i->gid = j->gid;
                         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 {
                         r = add_group(i);
                         if (r < 0)
@@ -1244,6 +1265,7 @@ static Item* item_free(Item *i) {
                 return NULL;
 
         free(i->name);
+        free(i->group_name);
         free(i->uid_path);
         free(i->gid_path);
         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;
                                 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->id_set_strict = true;
+                                        if (r < 0) {
+                                                if (valid_user_group_name(gid))
+                                                        i->group_name = TAKE_PTR(gid);
+                                                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);
                                 }
                                 if (!streq(resolved_id, "-")) {
diff --git a/test/TEST-21-SYSUSERS/test-13.expected-group b/test/TEST-21-SYSUSERS/test-13.expected-group
new file mode 100644 (file)
index 0000000..c78ea54
--- /dev/null
@@ -0,0 +1,5 @@
+hoge:x:300:
+baz:x:302:
+yyy:x:SYSTEM_GID_MAX:
+foo:x:301:
+ccc:x:305:
diff --git a/test/TEST-21-SYSUSERS/test-13.expected-passwd b/test/TEST-21-SYSUSERS/test-13.expected-passwd
new file mode 100644 (file)
index 0000000..ffc20a8
--- /dev/null
@@ -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
diff --git a/test/TEST-21-SYSUSERS/test-13.input b/test/TEST-21-SYSUSERS/test-13.input
new file mode 100644 (file)
index 0000000..bad2f09
--- /dev/null
@@ -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
diff --git a/test/TEST-21-SYSUSERS/test-14.expected-group b/test/TEST-21-SYSUSERS/test-14.expected-group
new file mode 100644 (file)
index 0000000..2e619bc
--- /dev/null
@@ -0,0 +1 @@
+pre:x:987:
diff --git a/test/TEST-21-SYSUSERS/test-14.expected-passwd b/test/TEST-21-SYSUSERS/test-14.expected-passwd
new file mode 100644 (file)
index 0000000..62ed4f5
--- /dev/null
@@ -0,0 +1 @@
+aaa:x:SYSTEM_UID_MAX:987::/:NOLOGIN
diff --git a/test/TEST-21-SYSUSERS/test-14.initial-group b/test/TEST-21-SYSUSERS/test-14.initial-group
new file mode 100644 (file)
index 0000000..2e619bc
--- /dev/null
@@ -0,0 +1 @@
+pre:x:987:
diff --git a/test/TEST-21-SYSUSERS/test-14.input b/test/TEST-21-SYSUSERS/test-14.input
new file mode 100644 (file)
index 0000000..0a11a2e
--- /dev/null
@@ -0,0 +1,4 @@
+# Ensure that a preexisting system group can be used as primary
+#
+#Type Name ID  GECOS HOMEDIR
+u aaa -:pre
index add16ea19fe1f1ec16dc1866b40047facb48d6bb..aed921e39ec3fbb01d4d10a8ba68b1dc5f1e7f05 100755 (executable)
@@ -23,6 +23,7 @@ preprocess() {
     # get this value from config.h, however the autopkgtest fails with
     # it
     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
     # uses pre-compiled binaries, so extract it from the systemd-sysusers
@@ -30,6 +31,7 @@ preprocess() {
     NOLOGIN=$(strings $(type -p systemd-sysusers) | grep nologin)
 
     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"
 }
 
diff --git a/test/TEST-21-SYSUSERS/unhappy-3.expected-err b/test/TEST-21-SYSUSERS/unhappy-3.expected-err
new file mode 100644 (file)
index 0000000..d55b366
--- /dev/null
@@ -0,0 +1 @@
+Group g1 not found.
diff --git a/test/TEST-21-SYSUSERS/unhappy-3.input b/test/TEST-21-SYSUSERS/unhappy-3.input
new file mode 100644 (file)
index 0000000..64e60dd
--- /dev/null
@@ -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 -