chiark / gitweb /
machine: make sure unpriviliged "machinectl status" can show the machine's OS version
[elogind.git] / src / sysusers / sysusers.c
index 53ff9509d85202df2c6354365b9efeac7af5f5c3..19568adf7a820581171b7ce723c8616ca175bafb 100644 (file)
@@ -24,6 +24,7 @@
 #include <grp.h>
 #include <shadow.h>
 #include <getopt.h>
 #include <grp.h>
 #include <shadow.h>
 #include <getopt.h>
+#include <utmp.h>
 
 #include "util.h"
 #include "hashmap.h"
 
 #include "util.h"
 #include "hashmap.h"
 #include "conf-files.h"
 #include "copy.h"
 #include "utf8.h"
 #include "conf-files.h"
 #include "copy.h"
 #include "utf8.h"
+#include "label.h"
+#include "fileio-label.h"
 
 typedef enum ItemType {
         ADD_USER = 'u',
         ADD_GROUP = 'g',
 
 typedef enum ItemType {
         ADD_USER = 'u',
         ADD_GROUP = 'g',
+        ADD_MEMBER = 'm',
 } ItemType;
 typedef struct Item {
         ItemType type;
 } ItemType;
 typedef struct Item {
         ItemType type;
@@ -53,12 +57,15 @@ typedef struct Item {
         bool gid_set:1;
         bool uid_set:1;
 
         bool gid_set:1;
         bool uid_set:1;
 
-        bool todo:1;
+        bool todo_user:1;
+        bool todo_group:1;
 } Item;
 
 static char *arg_root = NULL;
 
 static const char conf_file_dirs[] =
 } Item;
 
 static char *arg_root = NULL;
 
 static const char conf_file_dirs[] =
+        "/etc/sysusers.d\0"
+        "/run/sysusers.d\0"
         "/usr/local/lib/sysusers.d\0"
         "/usr/lib/sysusers.d\0"
 #ifdef HAVE_SPLIT_USR
         "/usr/local/lib/sysusers.d\0"
         "/usr/lib/sysusers.d\0"
 #ifdef HAVE_SPLIT_USR
@@ -68,6 +75,7 @@ static const char conf_file_dirs[] =
 
 static Hashmap *users = NULL, *groups = NULL;
 static Hashmap *todo_uids = NULL, *todo_gids = NULL;
 
 static Hashmap *users = NULL, *groups = NULL;
 static Hashmap *todo_uids = NULL, *todo_gids = NULL;
+static Hashmap *members = NULL;
 
 static Hashmap *database_uid = NULL, *database_user = NULL;
 static Hashmap *database_gid = NULL, *database_group = NULL;
 
 static Hashmap *database_uid = NULL, *database_user = NULL;
 static Hashmap *database_gid = NULL, *database_group = NULL;
@@ -187,8 +195,9 @@ static int load_group_database(void) {
         return 0;
 }
 
         return 0;
 }
 
-static int make_backup(const char *x) {
-        _cleanup_close_ int src = -1, dst = -1;
+static int make_backup(const char *target, const char *x) {
+        _cleanup_close_ int src = -1;
+        _cleanup_fclose_ FILE *dst = NULL;
         char *backup, *temp;
         struct timespec ts[2];
         struct stat st;
         char *backup, *temp;
         struct timespec ts[2];
         struct stat st;
@@ -205,30 +214,30 @@ static int make_backup(const char *x) {
         if (fstat(src, &st) < 0)
                 return -errno;
 
         if (fstat(src, &st) < 0)
                 return -errno;
 
-        temp = strappenda(x, ".XXXXXX");
-        dst = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC|O_NOCTTY);
-        if (dst < 0)
-                return dst;
+        r = fopen_temporary_label(target, x, &dst, &temp);
+        if (r < 0)
+                return r;
 
 
-        r = copy_bytes(src, dst);
+        r = copy_bytes(src, fileno(dst), (off_t) -1);
         if (r < 0)
                 goto fail;
 
         if (r < 0)
                 goto fail;
 
+        /* Don't fail on chmod() or chown(). If it stays owned by us
+         * and/or unreadable by others, then it isn't too bad... */
+
+        backup = strappenda(x, "-");
+
         /* Copy over the access mask */
         /* Copy over the access mask */
-        if (fchmod(dst, st.st_mode & 07777) < 0) {
-                r = -errno;
-                goto fail;
-        }
+        if (fchmod(fileno(dst), st.st_mode & 07777) < 0)
+                log_warning("Failed to change mode on %s: %m", backup);
 
 
-        /* Don't fail on chmod(). If it stays owned by us, then it
-         * isn't too bad... */
-        fchown(dst, st.st_uid, st.st_gid);
+        if (fchown(fileno(dst), st.st_uid, st.st_gid)< 0)
+                log_warning("Failed to change ownership of %s: %m", backup);
 
         ts[0] = st.st_atim;
         ts[1] = st.st_mtim;
 
         ts[0] = st.st_atim;
         ts[1] = st.st_mtim;
-        futimens(dst, ts);
+        futimens(fileno(dst), ts);
 
 
-        backup = strappenda(x, "-");
         if (rename(temp, backup) < 0)
                 goto fail;
 
         if (rename(temp, backup) < 0)
                 goto fail;
 
@@ -239,11 +248,62 @@ fail:
         return r;
 }
 
         return r;
 }
 
+static int putgrent_with_members(const struct group *gr, FILE *group) {
+        char **a;
+
+        assert(gr);
+        assert(group);
+
+        a = hashmap_get(members, gr->gr_name);
+        if (a) {
+                _cleanup_strv_free_ char **l = NULL;
+                bool added = false;
+                char **i;
+
+                l = strv_copy(gr->gr_mem);
+                if (!l)
+                        return -ENOMEM;
+
+                STRV_FOREACH(i, a) {
+                        if (strv_find(l, *i))
+                                continue;
+
+                        if (strv_extend(&l, *i) < 0)
+                                return -ENOMEM;
+
+                        added = true;
+                }
+
+                if (added) {
+                        struct group t;
+
+                        strv_uniq(l);
+                        strv_sort(l);
+
+                        t = *gr;
+                        t.gr_mem = l;
+
+                        errno = 0;
+                        if (putgrent(&t, group) != 0)
+                                return errno ? -errno : -EIO;
+
+                        return 1;
+                }
+        }
+
+        errno = 0;
+        if (putgrent(gr, group) != 0)
+                return errno ? -errno : -EIO;
+
+        return 0;
+}
+
 static int write_files(void) {
 
         _cleanup_fclose_ FILE *passwd = NULL, *group = NULL;
         _cleanup_free_ char *passwd_tmp = NULL, *group_tmp = NULL;
         const char *passwd_path = NULL, *group_path = NULL;
 static int write_files(void) {
 
         _cleanup_fclose_ FILE *passwd = NULL, *group = NULL;
         _cleanup_free_ char *passwd_tmp = NULL, *group_tmp = NULL;
         const char *passwd_path = NULL, *group_path = NULL;
+        bool group_changed = false;
         Iterator iterator;
         Item *i;
         int r;
         Iterator iterator;
         Item *i;
         int r;
@@ -251,11 +311,11 @@ static int write_files(void) {
         /* We don't patch /etc/shadow or /etc/gshadow here, since we
          * only create user accounts without passwords anyway. */
 
         /* We don't patch /etc/shadow or /etc/gshadow here, since we
          * only create user accounts without passwords anyway. */
 
-        if (hashmap_size(todo_gids) > 0) {
+        if (hashmap_size(todo_gids) > 0 || hashmap_size(members) > 0) {
                 _cleanup_fclose_ FILE *original = NULL;
 
                 group_path = fix_root("/etc/group");
                 _cleanup_fclose_ FILE *original = NULL;
 
                 group_path = fix_root("/etc/group");
-                r = fopen_temporary(group_path, &group, &group_tmp);
+                r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp);
                 if (r < 0)
                         goto finish;
 
                 if (r < 0)
                         goto finish;
 
@@ -279,7 +339,7 @@ static int write_files(void) {
                                  * duplicate entries. */
 
                                 i = hashmap_get(groups, gr->gr_name);
                                  * duplicate entries. */
 
                                 i = hashmap_get(groups, gr->gr_name);
-                                if (i && i->todo) {
+                                if (i && i->todo_group) {
                                         r = -EEXIST;
                                         goto finish;
                                 }
                                         r = -EEXIST;
                                         goto finish;
                                 }
@@ -289,10 +349,12 @@ static int write_files(void) {
                                         goto finish;
                                 }
 
                                         goto finish;
                                 }
 
-                                if (putgrent(gr, group) < 0) {
-                                        r = -errno;
+                                r = putgrent_with_members(gr, group);
+                                if (r < 0)
                                         goto finish;
                                         goto finish;
-                                }
+
+                                if (r > 0)
+                                        group_changed = true;
 
                                 errno = 0;
                         }
 
                                 errno = 0;
                         }
@@ -313,10 +375,11 @@ static int write_files(void) {
                                 .gr_passwd = (char*) "x",
                         };
 
                                 .gr_passwd = (char*) "x",
                         };
 
-                        if (putgrent(&n, group) < 0) {
-                                r = -errno;
+                        r = putgrent_with_members(&n, group);
+                        if (r < 0)
                                 goto finish;
                                 goto finish;
-                        }
+
+                        group_changed = true;
                 }
 
                 r = fflush_and_check(group);
                 }
 
                 r = fflush_and_check(group);
@@ -328,7 +391,7 @@ static int write_files(void) {
                 _cleanup_fclose_ FILE *original = NULL;
 
                 passwd_path = fix_root("/etc/passwd");
                 _cleanup_fclose_ FILE *original = NULL;
 
                 passwd_path = fix_root("/etc/passwd");
-                r = fopen_temporary(passwd_path, &passwd, &passwd_tmp);
+                r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
                 if (r < 0)
                         goto finish;
 
                 if (r < 0)
                         goto finish;
 
@@ -345,7 +408,7 @@ static int write_files(void) {
                         while ((pw = fgetpwent(original))) {
 
                                 i = hashmap_get(users, pw->pw_name);
                         while ((pw = fgetpwent(original))) {
 
                                 i = hashmap_get(users, pw->pw_name);
-                                if (i && i->todo) {
+                                if (i && i->todo_user) {
                                         r = -EEXIST;
                                         goto finish;
                                 }
                                         r = -EEXIST;
                                         goto finish;
                                 }
@@ -355,8 +418,9 @@ static int write_files(void) {
                                         goto finish;
                                 }
 
                                         goto finish;
                                 }
 
+                                errno = 0;
                                 if (putpwent(pw, passwd) < 0) {
                                 if (putpwent(pw, passwd) < 0) {
-                                        r = -errno;
+                                        r = errno ? -errno : -EIO;
                                         goto finish;
                                 }
 
                                         goto finish;
                                 }
 
@@ -392,8 +456,9 @@ static int write_files(void) {
                                 n.pw_dir = (char*) "/";
                         }
 
                                 n.pw_dir = (char*) "/";
                         }
 
-                        if (putpwent(&n, passwd) < 0) {
-                                r = -r;
+                        errno = 0;
+                        if (putpwent(&n, passwd) != 0) {
+                                r = errno ? -errno : -EIO;
                                 goto finish;
                         }
                 }
                                 goto finish;
                         }
                 }
@@ -404,20 +469,20 @@ static int write_files(void) {
         }
 
         /* Make a backup of the old files */
         }
 
         /* Make a backup of the old files */
-        if (group) {
-                r = make_backup(group_path);
+        if (group && group_changed) {
+                r = make_backup("/etc/group", group_path);
                 if (r < 0)
                         goto finish;
         }
 
         if (passwd) {
                 if (r < 0)
                         goto finish;
         }
 
         if (passwd) {
-                r = make_backup(passwd_path);
+                r = make_backup("/etc/passwd", passwd_path);
                 if (r < 0)
                         goto finish;
         }
 
         /* And make the new files count */
                 if (r < 0)
                         goto finish;
         }
 
         /* And make the new files count */
-        if (group) {
+        if (group && group_changed) {
                 if (rename(group_tmp, group_path) < 0) {
                         r = -errno;
                         goto finish;
                 if (rename(group_tmp, group_path) < 0) {
                         r = -errno;
                         goto finish;
@@ -437,15 +502,13 @@ static int write_files(void) {
                 passwd_tmp = NULL;
         }
 
                 passwd_tmp = NULL;
         }
 
-        return 0;
+        r = 0;
 
 finish:
 
 finish:
-        if (r < 0) {
-                if (passwd_tmp)
-                        unlink(passwd_tmp);
-                if (group_tmp)
-                        unlink(group_tmp);
-        }
+        if (passwd_tmp)
+                unlink(passwd_tmp);
+        if (group_tmp)
+                unlink(group_tmp);
 
         return r;
 }
 
         return r;
 }
@@ -480,7 +543,7 @@ static int uid_is_ok(uid_t uid, const char *name) {
                 p = getpwuid(uid);
                 if (p)
                         return 0;
                 p = getpwuid(uid);
                 if (p)
                         return 0;
-                if (errno != 0)
+                if (!IN_SET(errno, 0, ENOENT))
                         return -errno;
 
                 errno = 0;
                         return -errno;
 
                 errno = 0;
@@ -488,7 +551,7 @@ static int uid_is_ok(uid_t uid, const char *name) {
                 if (g) {
                         if (!streq(g->gr_name, name))
                                 return 0;
                 if (g) {
                         if (!streq(g->gr_name, name))
                                 return 0;
-                } else if (errno != 0)
+                } else if (!IN_SET(errno, 0, ENOENT))
                         return -errno;
         }
 
                         return -errno;
         }
 
@@ -573,7 +636,7 @@ static int add_user(Item *i) {
         z = hashmap_get(database_user, i->name);
         if (z) {
                 log_debug("User %s already exists.", i->name);
         z = hashmap_get(database_user, i->name);
         if (z) {
                 log_debug("User %s already exists.", i->name);
-                i->uid = PTR_TO_GID(z);
+                i->uid = PTR_TO_UID(z);
                 i->uid_set = true;
                 return 0;
         }
                 i->uid_set = true;
                 return 0;
         }
@@ -594,7 +657,7 @@ static int add_user(Item *i) {
                         i->description = strdup(p->pw_gecos);
                         return 0;
                 }
                         i->description = strdup(p->pw_gecos);
                         return 0;
                 }
-                if (errno != 0) {
+                if (!IN_SET(errno, 0, ENOENT)) {
                         log_error("Failed to check if user %s already exists: %m", i->name);
                         return -errno;
                 }
                         log_error("Failed to check if user %s already exists: %m", i->name);
                         return -errno;
                 }
@@ -606,7 +669,7 @@ static int add_user(Item *i) {
                         log_error("User %s already exists in shadow database, but not in user database.", i->name);
                         return -EBADMSG;
                 }
                         log_error("User %s already exists in shadow database, but not in user database.", i->name);
                         return -EBADMSG;
                 }
-                if (errno != 0) {
+                if (!IN_SET(errno, 0, ENOENT)) {
                         log_error("Failed to check if user %s already exists in shadow database: %m", i->name);
                         return -errno;
                 }
                         log_error("Failed to check if user %s already exists in shadow database: %m", i->name);
                         return -errno;
                 }
@@ -691,7 +754,7 @@ static int add_user(Item *i) {
         if (r < 0)
                 return log_oom();
 
         if (r < 0)
                 return log_oom();
 
-        i->todo = true;
+        i->todo_user = true;
         log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid);
 
         return 0;
         log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid);
 
         return 0;
@@ -719,14 +782,14 @@ static int gid_is_ok(gid_t gid) {
                 g = getgrgid(gid);
                 if (g)
                         return 0;
                 g = getgrgid(gid);
                 if (g)
                         return 0;
-                if (errno != 0)
+                if (!IN_SET(errno, 0, ENOENT))
                         return -errno;
 
                 errno = 0;
                 p = getpwuid((uid_t) gid);
                 if (p)
                         return 0;
                         return -errno;
 
                 errno = 0;
                 p = getpwuid((uid_t) gid);
                 if (p)
                         return 0;
-                if (errno != 0)
+                if (!IN_SET(errno, 0, ENOENT))
                         return -errno;
         }
 
                         return -errno;
         }
 
@@ -760,7 +823,7 @@ static int add_group(Item *i) {
                         i->gid_set = true;
                         return 0;
                 }
                         i->gid_set = true;
                         return 0;
                 }
-                if (errno != 0) {
+                if (!IN_SET(errno, 0, ENOENT)) {
                         log_error("Failed to check if group %s already exists: %m", i->name);
                         return -errno;
                 }
                         log_error("Failed to check if group %s already exists: %m", i->name);
                         return -errno;
                 }
@@ -844,7 +907,7 @@ static int add_group(Item *i) {
         if (r < 0)
                 return log_oom();
 
         if (r < 0)
                 return log_oom();
 
-        i->todo = true;
+        i->todo_group = true;
         log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid);
 
         return 0;
         log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid);
 
         return 0;
@@ -889,9 +952,10 @@ static int process_item(Item *i) {
 
                 return add_group(i);
         }
 
                 return add_group(i);
         }
-        }
 
 
-        assert_not_reached("Unknown item type");
+        default:
+                assert_not_reached("Unknown item type");
+        }
 }
 
 static void item_free(Item *i) {
 }
 
 static void item_free(Item *i) {
@@ -908,6 +972,74 @@ static void item_free(Item *i) {
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
 
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
 
+static int add_implicit(void) {
+        char *g, **l;
+        Iterator iterator;
+        int r;
+
+        /* Implicitly create additional users and groups, if they were listed in "m" lines */
+
+        HASHMAP_FOREACH_KEY(l, g, members, iterator) {
+                Item *i;
+                char **m;
+
+                i = hashmap_get(groups, g);
+                if (!i) {
+                        _cleanup_(item_freep) Item *j = NULL;
+
+                        r = hashmap_ensure_allocated(&groups, string_hash_func, string_compare_func);
+                        if (r < 0)
+                                return log_oom();
+
+                        j = new0(Item, 1);
+                        if (!j)
+                                return log_oom();
+
+                        j->type = ADD_GROUP;
+                        j->name = strdup(g);
+                        if (!j->name)
+                                return log_oom();
+
+                        r = hashmap_put(groups, j->name, j);
+                        if (r < 0)
+                                return log_oom();
+
+                        log_debug("Adding implicit group '%s' due to m line", j->name);
+                        j = NULL;
+                }
+
+                STRV_FOREACH(m, l) {
+
+                        i = hashmap_get(users, *m);
+                        if (!i) {
+                                _cleanup_(item_freep) Item *j = NULL;
+
+                                r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
+                                if (r < 0)
+                                        return log_oom();
+
+                                j = new0(Item, 1);
+                                if (!j)
+                                        return log_oom();
+
+                                j->type = ADD_USER;
+                                j->name = strdup(*m);
+                                if (!j->name)
+                                        return log_oom();
+
+                                r = hashmap_put(users, j->name, j);
+                                if (r < 0)
+                                        return log_oom();
+
+                                log_debug("Adding implicit user '%s' due to m line", j->name);
+                                j = NULL;
+                        }
+                }
+        }
+
+        return 0;
+}
+
 static bool item_equal(Item *a, Item *b) {
         assert(a);
         assert(b);
 static bool item_equal(Item *a, Item *b) {
         assert(a);
         assert(b);
@@ -969,6 +1101,9 @@ static bool valid_user_group_name(const char *u) {
         if ((size_t) (i-u) > (size_t) sz)
                 return false;
 
         if ((size_t) (i-u) > (size_t) sz)
                 return false;
 
+        if ((size_t) (i-u) > UT_NAMESIZE - 1)
+                return false;
+
         return true;
 }
 
         return true;
 }
 
@@ -977,7 +1112,11 @@ static bool valid_gecos(const char *d) {
         if (!utf8_is_valid(d))
                 return false;
 
         if (!utf8_is_valid(d))
                 return false;
 
-        if (strpbrk(d, ":\n"))
+        if (string_has_cc(d, NULL))
+                return false;
+
+        /* Colons are used as field separators, and hence not OK */
+        if (strchr(d, ':'))
                 return false;
 
         return true;
                 return false;
 
         return true;
@@ -993,7 +1132,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                 {}
         };
 
                 {}
         };
 
-        _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL;
+        _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL;
         _cleanup_(item_freep) Item *i = NULL;
         Item *existing;
         Hashmap *h;
         _cleanup_(item_freep) Item *i = NULL;
         Item *existing;
         Hashmap *h;
@@ -1019,90 +1158,180 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                 return -EINVAL;
         }
 
                 return -EINVAL;
         }
 
-        if (!IN_SET(action[0], ADD_USER, ADD_GROUP)) {
+        if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER)) {
                 log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]);
                 return -EBADMSG;
         }
 
                 log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]);
                 return -EBADMSG;
         }
 
-        i = new0(Item, 1);
-        if (!i)
-                return log_oom();
-
-        i->type = action[0];
-
-        r = specifier_printf(name, specifier_table, NULL, &i->name);
+        r = specifier_printf(name, specifier_table, NULL, &resolved_name);
         if (r < 0) {
                 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
                 return r;
         }
 
         if (r < 0) {
                 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
                 return r;
         }
 
-        if (!valid_user_group_name(i->name)) {
-                log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, i->name);
+        if (!valid_user_group_name(resolved_name)) {
+                log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name);
                 return -EINVAL;
         }
 
         if (n >= 0) {
                 n += strspn(buffer+n, WHITESPACE);
                 return -EINVAL;
         }
 
         if (n >= 0) {
                 n += strspn(buffer+n, WHITESPACE);
-                if (buffer[n] != 0 && (buffer[n] != '-' || buffer[n+1] != 0)) {
-                        i->description = unquote(buffer+n, "\"");
-                        if (!i->description)
-                                return log_oom();
 
 
-                        if (!valid_gecos(i->description)) {
-                                log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, i->description);
-                                return -EINVAL;
-                        }
-                }
+                if (STR_IN_SET(buffer + n, "", "-"))
+                        n = -1;
         }
 
         }
 
-        if (id && !streq(id, "-")) {
+        switch (action[0]) {
 
 
-                if (path_is_absolute(id)) {
-                        char *p;
+        case ADD_MEMBER: {
+                _cleanup_free_ char *resolved_id = NULL;
+                char **l;
 
 
-                        p = strdup(id);
-                        if (!p)
-                                return log_oom();
+                r = hashmap_ensure_allocated(&members, string_hash_func, string_compare_func);
+                if (r < 0)
+                        return log_oom();
 
 
-                        path_kill_slashes(p);
+                /* Try to extend an existing member or group item */
 
 
-                        if (i->type == ADD_USER)
-                                i->uid_path = p;
-                        else
-                                i->gid_path = p;
+                if (!id || streq(id, "-")) {
+                        log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line);
+                        return -EINVAL;
+                }
 
 
-                } else if (i->type == ADD_USER) {
-                        r = parse_uid(id, &i->uid);
-                        if (r < 0) {
-                                log_error("Failed to parse UID: %s", id);
-                                return -EBADMSG;
-                        }
+                r = specifier_printf(id, specifier_table, NULL, &resolved_id);
+                if (r < 0) {
+                        log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
+                        return r;
+                }
 
 
-                        i->uid_set = true;
+                if (!valid_user_group_name(resolved_id)) {
+                        log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id);
+                        return -EINVAL;
+                }
+
+                if (n >= 0) {
+                        log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line);
+                        return -EINVAL;
+                }
+
+                l = 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);
+                        if (r < 0)
+                                return log_oom();
+
+                        resolved_name = NULL;
 
 
+                        assert_se(hashmap_update(members, resolved_id, l) >= 0);
                 } else {
                 } else {
-                        assert(i->type == ADD_GROUP);
+                        /* No list for this group name exists yet, create one */
 
 
-                        r = parse_gid(id, &i->gid);
+                        l = new0(char *, 2);
+                        if (!l)
+                                return -ENOMEM;
+
+                        l[0] = resolved_name;
+                        l[1] = NULL;
+
+                        r = hashmap_put(members, resolved_id, l);
                         if (r < 0) {
                         if (r < 0) {
-                                log_error("Failed to parse GID: %s", id);
-                                return -EBADMSG;
+                                free(l);
+                                return log_oom();
                         }
 
                         }
 
-                        i->gid_set = true;
+                        resolved_id = resolved_name = NULL;
                 }
                 }
+
+                return 0;
         }
 
         }
 
-        if (i->type == ADD_USER) {
+        case ADD_USER:
                 r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
                 r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
+                if (r < 0)
+                        return log_oom();
+
+                i = new0(Item, 1);
+                if (!i)
+                        return log_oom();
+
+                if (id && !streq(id, "-")) {
+
+                        if (path_is_absolute(id)) {
+                                i->uid_path = strdup(id);
+                                if (!i->uid_path)
+                                        return log_oom();
+
+                                path_kill_slashes(i->uid_path);
+
+                        } else {
+                                r = parse_uid(id, &i->uid);
+                                if (r < 0) {
+                                        log_error("Failed to parse UID: %s", id);
+                                        return -EBADMSG;
+                                }
+
+                                i->uid_set = true;
+                        }
+                }
+
+                if (n >= 0) {
+                        i->description = unquote(buffer+n, "\"");
+                        if (!i->description)
+                                return log_oom();
+
+                        if (!valid_gecos(i->description)) {
+                                log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, i->description);
+                                return -EINVAL;
+                        }
+                }
+
                 h = users;
                 h = users;
-        } else {
-                assert(i->type == ADD_GROUP);
+                break;
+
+        case ADD_GROUP:
                 r = hashmap_ensure_allocated(&groups, string_hash_func, string_compare_func);
                 r = hashmap_ensure_allocated(&groups, string_hash_func, string_compare_func);
+                if (r < 0)
+                        return log_oom();
+
+                if (n >= 0) {
+                        log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line);
+                        return -EINVAL;
+                }
+
+                i = new0(Item, 1);
+                if (!i)
+                        return log_oom();
+
+                if (id && !streq(id, "-")) {
+
+                        if (path_is_absolute(id)) {
+                                i->gid_path = strdup(id);
+                                if (!i->gid_path)
+                                        return log_oom();
+
+                                path_kill_slashes(i->gid_path);
+                        } else {
+                                r = parse_gid(id, &i->gid);
+                                if (r < 0) {
+                                        log_error("Failed to parse GID: %s", id);
+                                        return -EBADMSG;
+                                }
+
+                                i->gid_set = true;
+                        }
+                }
+
+
                 h = groups;
                 h = groups;
+                break;
+        default:
+                return -EBADMSG;
         }
         }
-        if (r < 0)
-                return log_oom();
+
+        i->type = action[0];
+        i->name = resolved_name;
+        resolved_name = NULL;
 
         existing = hashmap_get(h, i->name);
         if (existing) {
 
         existing = hashmap_get(h, i->name);
         if (existing) {
@@ -1115,10 +1344,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
         }
 
         r = hashmap_put(h, i->name, i);
         }
 
         r = hashmap_put(h, i->name, i);
-        if (r < 0) {
-                log_error("Failed to insert item %s: %s", i->name, strerror(-r));
-                return r;
-        }
+        if (r < 0)
+                return log_oom();
 
         i = NULL;
         return 0;
 
         i = NULL;
         return 0;
@@ -1165,42 +1392,6 @@ static int read_config_file(const char *fn, bool ignore_enoent) {
         return r;
 }
 
         return r;
 }
 
-static int take_lock(void) {
-
-        struct flock flock = {
-                .l_type = F_WRLCK,
-                .l_whence = SEEK_SET,
-                .l_start = 0,
-                .l_len = 0,
-        };
-
-        const char *path;
-        int fd, r;
-
-        /* This is roughly the same as lckpwdf(), but not as awful. We
-         * don't want to use alarm() and signals, hence we implement
-         * our own trivial version of this.
-         *
-         * Note that shadow-utils also takes per-database locks in
-         * addition to lckpwdf(). However, we don't given that they
-         * are redundant as they they invoke lckpwdf() first and keep
-         * it during everything they do. The per-database locks are
-         * awfully racy, and thus we just won't do them. */
-
-        path = fix_root("/etc/.pwd.lock");
-        fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
-        if (fd < 0)
-                return -errno;
-
-        r = fcntl(fd, F_SETLKW, &flock);
-        if (r < 0) {
-                safe_close(fd);
-                return -errno;
-        }
-
-        return fd;
-}
-
 static void free_database(Hashmap *by_name, Hashmap *by_id) {
         char *name;
 
 static void free_database(Hashmap *by_name, Hashmap *by_id) {
         char *name;
 
@@ -1222,16 +1413,13 @@ static void free_database(Hashmap *by_name, Hashmap *by_id) {
         hashmap_free(by_id);
 }
 
         hashmap_free(by_id);
 }
 
-static int help(void) {
-
+static void help(void) {
         printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
                "Creates system user accounts.\n\n"
                "  -h --help                 Show this help\n"
                "     --version              Show package version\n"
         printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
                "Creates system user accounts.\n\n"
                "  -h --help                 Show this help\n"
                "     --version              Show package version\n"
-               "     --root=PATH            Operate on an alternate filesystem root\n",
-               program_invocation_short_name);
-
-        return 0;
+               "     --root=PATH            Operate on an alternate filesystem root\n"
+               , program_invocation_short_name);
 }
 
 static int parse_argv(int argc, char *argv[]) {
 }
 
 static int parse_argv(int argc, char *argv[]) {
@@ -1253,12 +1441,13 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
 
                 switch (c) {
 
                 case 'h':
 
                 switch (c) {
 
                 case 'h':
-                        return help();
+                        help();
+                        return 0;
 
                 case ARG_VERSION:
                         puts(PACKAGE_STRING);
 
                 case ARG_VERSION:
                         puts(PACKAGE_STRING);
@@ -1280,7 +1469,6 @@ static int parse_argv(int argc, char *argv[]) {
                 default:
                         assert_not_reached("Unhandled option");
                 }
                 default:
                         assert_not_reached("Unhandled option");
                 }
-        }
 
         return 1;
 }
 
         return 1;
 }
@@ -1291,6 +1479,7 @@ int main(int argc, char *argv[]) {
         Iterator iterator;
         int r, k;
         Item *i;
         Iterator iterator;
         int r, k;
         Item *i;
+        char *n;
 
         r = parse_argv(argc, argv);
         if (r <= 0)
 
         r = parse_argv(argc, argv);
         if (r <= 0)
@@ -1302,7 +1491,11 @@ int main(int argc, char *argv[]) {
 
         umask(0022);
 
 
         umask(0022);
 
-        r = 0;
+        r = label_init(NULL);
+        if (r < 0) {
+                log_error("SELinux setup failed: %s", strerror(-r));
+                goto finish;
+        }
 
         if (optind < argc) {
                 int j;
 
         if (optind < argc) {
                 int j;
@@ -1329,7 +1522,11 @@ int main(int argc, char *argv[]) {
                 }
         }
 
                 }
         }
 
-        lock = take_lock();
+        r = add_implicit();
+        if (r < 0)
+                goto finish;
+
+        lock = take_password_lock(arg_root);
         if (lock < 0) {
                 log_error("Failed to take lock: %s", strerror(-lock));
                 goto finish;
         if (lock < 0) {
                 log_error("Failed to take lock: %s", strerror(-lock));
                 goto finish;
@@ -1364,8 +1561,14 @@ finish:
         while ((i = hashmap_steal_first(users)))
                 item_free(i);
 
         while ((i = hashmap_steal_first(users)))
                 item_free(i);
 
+        while ((n = hashmap_first_key(members))) {
+                strv_free(hashmap_steal_first(members));
+                free(n);
+        }
+
         hashmap_free(groups);
         hashmap_free(users);
         hashmap_free(groups);
         hashmap_free(users);
+        hashmap_free(members);
         hashmap_free(todo_uids);
         hashmap_free(todo_gids);
 
         hashmap_free(todo_uids);
         hashmap_free(todo_gids);