]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/sysusers/sysusers.c
Merge pull request #13365 from keszybz/fix-commits-from-pr-13246
[thirdparty/systemd.git] / src / sysusers / sysusers.c
index df28bcfd720df874fcacb4ad18c8e430353e799d..1fc1b0ae96162b3af5c1181cf9cdb02cab18a738 100644 (file)
@@ -244,7 +244,7 @@ static int make_backup(const char *target, const char *x) {
         return 0;
 
 fail:
-        unlink(temp);
+        (void) unlink(temp);
         return r;
 }
 
@@ -361,7 +361,7 @@ static int rename_and_apply_smack(const char *temp_path, const char *dest_path)
 }
 
 static const char* default_shell(uid_t uid) {
-        return uid == 0 ? "/bin/sh" : "/sbin/nologin";
+        return uid == 0 ? "/bin/sh" : NOLOGIN;
 }
 
 static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char **tmpfile_path) {
@@ -389,15 +389,15 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
                 while ((r = fgetpwent_sane(original, &pw)) > 0) {
 
                         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 (i && i->todo_user)
+                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
+                                                       "%s: User \"%s\" already exists.",
+                                                       passwd_path, pw->pw_name);
 
-                        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;
-                        }
+                        if (ordered_hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid)))
+                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
+                                                       "%s: Detected collision for UID " UID_FMT ".",
+                                                       passwd_path, pw->pw_uid);
 
                         /* Make sure we keep the NIS entries (if any) at the end. */
                         if (IN_SET(pw->pw_name[0], '+', '-'))
@@ -428,7 +428,7 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
                         .pw_passwd = (char*) "x",
 
                         /* We default to the root directory as home */
-                        .pw_dir = i->home ? i->home : (char*) "/",
+                        .pw_dir = i->home ?: (char*) "/",
 
                         /* Initialize the shell to nologin, with one exception:
                          * for root we patch in something special */
@@ -522,13 +522,13 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
         ORDERED_HASHMAP_FOREACH(i, todo_uids, iterator) {
                 struct spwd n = {
                         .sp_namp = i->name,
-                        .sp_pwdp = (char*) "!!",
+                        .sp_pwdp = (char*) "!!", /* lock this password, and make it invalid */
                         .sp_lstchg = lstchg,
                         .sp_min = -1,
                         .sp_max = -1,
                         .sp_warn = -1,
                         .sp_inact = -1,
-                        .sp_expire = -1,
+                        .sp_expire = i->uid == 0 ? -1 : 1, /* lock account as a whole, unless this is root */
                         .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
                 };
 
@@ -592,15 +592,15 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
                          * step that we don't generate duplicate entries. */
 
                         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 (i && i->todo_group)
+                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
+                                                       "%s: Group \"%s\" already exists.",
+                                                       group_path, gr->gr_name);
 
-                        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;
-                        }
+                        if (ordered_hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid)))
+                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
+                                                       "%s: Detected collision for GID " GID_FMT ".",
+                                                       group_path, gr->gr_gid);
 
                         /* Make sure we keep the NIS entries (if any) at the end. */
                         if (IN_SET(gr->gr_name[0], '+', '-'))
@@ -687,10 +687,10 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
                 while ((r = fgetsgent_sane(original, &sg)) > 0) {
 
                         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;
-                        }
+                        if (i && i->todo_group)
+                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
+                                                       "%s: Group \"%s\" already exists.",
+                                                       gshadow_path, sg->sg_namp);
 
                         r = putsgent_with_members(sg, gshadow);
                         if (r < 0)
@@ -1021,10 +1021,8 @@ static int add_user(Item *i) {
         if (!i->uid_set) {
                 for (;;) {
                         r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
-                        if (r < 0) {
-                                log_error("No free user ID available for %s.", i->name);
-                                return r;
-                        }
+                        if (r < 0)
+                                return log_error_errno(r, "No free user ID available for %s.", i->name);
 
                         r = uid_is_ok(search_uid, i->name, true);
                         if (r < 0)
@@ -1178,10 +1176,8 @@ static int add_group(Item *i) {
                 for (;;) {
                         /* We look for new GIDs in the UID pool! */
                         r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
-                        if (r < 0) {
-                                log_error("No free group ID available for %s.", i->name);
-                                return r;
-                        }
+                        if (r < 0)
+                                return log_error_errno(r, "No free group ID available for %s.", i->name);
 
                         r = gid_is_ok(search_uid);
                         if (r < 0)
@@ -1362,8 +1358,6 @@ static bool item_equal(Item *a, Item *b) {
         return true;
 }
 
-DEFINE_PRIVATE_HASH_OPS_FULL(members_hash_ops, char, string_hash_func, string_compare_func, free, char*, strv_free);
-
 static int parse_line(const char *fname, unsigned line, const char *buffer) {
 
         static const Specifier specifier_table[] = {
@@ -1381,7 +1375,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                 *id = NULL, *resolved_id = NULL,
                 *description = NULL, *resolved_description = NULL,
                 *home = NULL, *resolved_home = NULL,
-                *shell, *resolved_shell = NULL;
+                *shell = NULL, *resolved_shell = NULL;
         _cleanup_(item_freep) Item *i = NULL;
         Item *existing;
         OrderedHashmap *h;
@@ -1394,207 +1388,161 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
 
         /* Parse columns */
         p = buffer;
-        r = extract_many_words(&p, NULL, EXTRACT_QUOTES,
+        r = extract_many_words(&p, NULL, EXTRACT_UNQUOTE,
                                &action, &name, &id, &description, &home, &shell, NULL);
-        if (r < 0) {
-                log_error("[%s:%u] Syntax error.", fname, line);
-                return r;
-        }
-        if (r < 2) {
-                log_error("[%s:%u] Missing action and name columns.", fname, line);
-                return -EINVAL;
-        }
-        if (!isempty(p)) {
-                log_error("[%s:%u] Trailing garbage.", fname, line);
-                return -EINVAL;
-        }
+        if (r < 0)
+                return log_error_errno(r, "[%s:%u] Syntax error.", fname, line);
+        if (r < 2)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "[%s:%u] Missing action and name columns.", fname, line);
+        if (!isempty(p))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "[%s:%u] Trailing garbage.", fname, line);
 
         /* Verify action */
-        if (strlen(action) != 1) {
-                log_error("[%s:%u] Unknown modifier '%s'", fname, line, action);
-                return -EINVAL;
-        }
+        if (strlen(action) != 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "[%s:%u] Unknown modifier '%s'", fname, line, action);
 
-        if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE)) {
-                log_error("[%s:%u] Unknown command type '%c'.", fname, line, action[0]);
-                return -EBADMSG;
-        }
+        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]);
 
         /* Verify name */
-        if (isempty(name) || streq(name, "-"))
+        if (empty_or_dash(name))
                 name = mfree(name);
 
         if (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_errno(r, "[%s:%u] Failed to replace specifiers: %s", fname, line, 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 (!valid_user_group_name(resolved_name))
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "[%s:%u] '%s' is not a valid user or group name.",
+                                               fname, line, resolved_name);
         }
 
         /* Verify id */
-        if (isempty(id) || streq(id, "-"))
+        if (empty_or_dash(id))
                 id = mfree(id);
 
         if (id) {
                 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;
-                }
+                if (r < 0)
+                        return log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s",
+                                               fname, line, name);
         }
 
         /* Verify description */
-        if (isempty(description) || streq(description, "-"))
+        if (empty_or_dash(description))
                 description = mfree(description);
 
         if (description) {
                 r = specifier_printf(description, specifier_table, NULL, &resolved_description);
-                if (r < 0) {
-                        log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, description);
-                        return r;
-                }
+                if (r < 0)
+                        return log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s",
+                                               fname, line, description);
 
-                if (!valid_gecos(resolved_description)) {
-                        log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, resolved_description);
-                        return -EINVAL;
-                }
+                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);
         }
 
         /* Verify home */
-        if (isempty(home) || streq(home, "-"))
+        if (empty_or_dash(home))
                 home = mfree(home);
 
         if (home) {
                 r = specifier_printf(home, specifier_table, NULL, &resolved_home);
-                if (r < 0) {
-                        log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, home);
-                        return r;
-                }
+                if (r < 0)
+                        return log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s",
+                                               fname, line, home);
 
-                if (!valid_home(resolved_home)) {
-                        log_error("[%s:%u] '%s' is not a valid home directory field.", fname, line, resolved_home);
-                        return -EINVAL;
-                }
+                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);
         }
 
         /* Verify shell */
-        if (isempty(shell) || streq(shell, "-"))
+        if (empty_or_dash(shell))
                 shell = mfree(shell);
 
         if (shell) {
                 r = specifier_printf(shell, specifier_table, NULL, &resolved_shell);
-                if (r < 0) {
-                        log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, shell);
-                        return r;
-                }
+                if (r < 0)
+                        return log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s",
+                                               fname, line, shell);
 
-                if (!valid_shell(resolved_shell)) {
-                        log_error("[%s:%u] '%s' is not a valid login shell field.", fname, line, resolved_shell);
-                        return -EINVAL;
-                }
+                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);
         }
 
         switch (action[0]) {
 
         case ADD_RANGE:
-                if (resolved_name) {
-                        log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname, line);
-                        return -EINVAL;
-                }
-
-                if (!resolved_id) {
-                        log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname, line);
-                        return -EINVAL;
-                }
-
-                if (description || home || shell) {
-                        log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
-                                  fname, line, action[0],
-                                  description ? "GECOS" : home ? "home directory" : "login shell");
-                        return -EINVAL;
-                }
+                if (resolved_name)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "[%s:%u] Lines of type 'r' don't take a name field.",
+                                               fname, line);
+
+                if (!resolved_id)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "[%s:%u] Lines of type 'r' require a ID range in the third field.",
+                                               fname, line);
+
+                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");
 
                 r = uid_range_add_str(&uid_range, &n_uid_range, resolved_id);
-                if (r < 0) {
-                        log_error("[%s:%u] Invalid UID range %s.", fname, line, resolved_id);
-                        return -EINVAL;
-                }
+                if (r < 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "[%s:%u] Invalid UID range %s.", fname, line, resolved_id);
 
                 return 0;
 
         case ADD_MEMBER: {
-                char **l;
-
                 /* Try to extend an existing member or group item */
-                if (!name) {
-                        log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname, line);
-                        return -EINVAL;
-                }
-
-                if (!resolved_id) {
-                        log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line);
-                        return -EINVAL;
-                }
-
-                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 (description || home || shell) {
-                        log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
-                                  fname, line, action[0],
-                                  description ? "GECOS" : home ? "home directory" : "login shell");
-                        return -EINVAL;
-                }
-
-                r = ordered_hashmap_ensure_allocated(&members, &members_hash_ops);
+                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);
+
+                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);
+
+                if (!valid_user_group_name(resolved_id))
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "[%s:%u] '%s' is not a valid user or group name.",
+                                               fname, line, 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");
+
+                r = string_strv_ordered_hashmap_put(&members, resolved_id, resolved_name);
                 if (r < 0)
-                        return log_oom();
-
-                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);
-                        if (r < 0)
-                                return log_oom();
-
-                        resolved_name = NULL;
-
-                        assert_se(ordered_hashmap_update(members, resolved_id, l) >= 0);
-                } else {
-                        /* No list for this group name exists yet, create one */
-
-                        l = new0(char *, 2);
-                        if (!l)
-                                return -ENOMEM;
-
-                        l[0] = resolved_name;
-                        l[1] = NULL;
-
-                        r = ordered_hashmap_put(members, resolved_id, l);
-                        if (r < 0) {
-                                free(l);
-                                return log_oom();
-                        }
-
-                        resolved_id = resolved_name = NULL;
-                }
+                        return log_error_errno(r, "Failed to store mapping for %s: %m", resolved_id);
 
                 return 0;
         }
 
         case ADD_USER:
-                if (!name) {
-                        log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname, line);
-                        return -EINVAL;
-                }
+                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);
 
                 r = ordered_hashmap_ensure_allocated(&users, &item_hash_ops);
                 if (r < 0)
@@ -1635,17 +1583,16 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                 break;
 
         case ADD_GROUP:
-                if (!name) {
-                        log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname, line);
-                        return -EINVAL;
-                }
+                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);
 
-                if (description || home || shell) {
-                        log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
-                                  fname, line, action[0],
-                                  description ? "GECOS" : home ? "home directory" : "login shell");
-                        return -EINVAL;
-                }
+                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");
 
                 r = ordered_hashmap_ensure_allocated(&groups, &item_hash_ops);
                 if (r < 0)
@@ -1680,7 +1627,6 @@ 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("Two or more conflicting lines for %s configured, ignoring.", i->name);