]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
tmpfiles: optionally, read /etc/passwd + /etc/group without NSS
authorLennart Poettering <lennart@poettering.net>
Tue, 5 May 2020 20:45:54 +0000 (22:45 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 7 May 2020 14:35:20 +0000 (16:35 +0200)
There are two libc APIs for accessing the user database: NSS/getpwuid(),
and fgetpwent(). if we run in --root= mode (i.e. "offline" mode), let's
use the latter. Otherwise the former. This means tmpfiles can use the
database included in the root environment for chowning, which is a lot
more appropriate.

Fixes: #14806
meson.build
src/tmpfiles/offline-passwd.c [new file with mode: 0644]
src/tmpfiles/offline-passwd.h [new file with mode: 0644]
src/tmpfiles/tmpfiles.c

index a922f9a2f1c114daaa42cfb4db4e10a8cc27a374..0600bdeff5694077ae4c5996aaa03ca1ffef12a3 100644 (file)
@@ -2884,6 +2884,8 @@ if conf.get('ENABLE_TMPFILES') == 1
         exe = executable(
                 'systemd-tmpfiles',
                 'src/tmpfiles/tmpfiles.c',
+                'src/tmpfiles/offline-passwd.c',
+                'src/tmpfiles/offline-passwd.h',
                 include_directories : includes,
                 link_with : [libshared],
                 dependencies : [libacl],
diff --git a/src/tmpfiles/offline-passwd.c b/src/tmpfiles/offline-passwd.c
new file mode 100644 (file)
index 0000000..8ac5431
--- /dev/null
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "fd-util.h"
+#include "offline-passwd.h"
+#include "path-util.h"
+#include "user-util.h"
+
+DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(uid_gid_hash_ops, char, string_hash_func, string_compare_func, free);
+
+int name_to_uid_offline(
+                const char *root,
+                const char *user,
+                uid_t *ret_uid,
+                Hashmap **cache) {
+
+        void *found;
+        int r;
+
+        assert(user);
+        assert(ret_uid);
+        assert(cache);
+
+        if (!*cache) {
+                _cleanup_(hashmap_freep) Hashmap *uid_by_name = NULL;
+                _cleanup_fclose_ FILE *f = NULL;
+                struct passwd *pw;
+                const char *passwd_path;
+
+                passwd_path = prefix_roota(root, "/etc/passwd");
+                f = fopen(passwd_path, "re");
+                if (!f)
+                        return errno == ENOENT ? -ESRCH : -errno;
+
+                uid_by_name = hashmap_new(&uid_gid_hash_ops);
+                if (!uid_by_name)
+                        return -ENOMEM;
+
+                while ((r = fgetpwent_sane(f, &pw)) > 0) {
+                        _cleanup_free_ char *n = NULL;
+
+                        n = strdup(pw->pw_name);
+                        if (!n)
+                                return -ENOMEM;
+
+                        r = hashmap_put(uid_by_name, n, UID_TO_PTR(pw->pw_uid));
+                        if (r == -EEXIST) {
+                                log_warning_errno(r, "Duplicate entry in %s for %s: %m", passwd_path, pw->pw_name);
+                                continue;
+                        }
+                        if (r < 0)
+                                return r;
+
+                        TAKE_PTR(n);
+                }
+
+                *cache = TAKE_PTR(uid_by_name);
+        }
+
+        found = hashmap_get(*cache, user);
+        if (!found)
+                return -ESRCH;
+
+        *ret_uid = PTR_TO_UID(found);
+        return 0;
+}
+
+int name_to_gid_offline(
+                const char *root,
+                const char *group,
+                gid_t *ret_gid,
+                Hashmap **cache) {
+
+        void *found;
+        int r;
+
+        assert(group);
+        assert(ret_gid);
+        assert(cache);
+
+        if (!*cache) {
+                _cleanup_(hashmap_freep) Hashmap *gid_by_name = NULL;
+                _cleanup_fclose_ FILE *f = NULL;
+                struct group *gr;
+                const char *group_path;
+
+                group_path = prefix_roota(root, "/etc/group");
+                f = fopen(group_path, "re");
+                if (!f)
+                        return errno == ENOENT ? -ESRCH : -errno;
+
+                gid_by_name = hashmap_new(&uid_gid_hash_ops);
+                if (!gid_by_name)
+                        return -ENOMEM;
+
+                while ((r = fgetgrent_sane(f, &gr)) > 0) {
+                        _cleanup_free_ char *n = NULL;
+
+                        n = strdup(gr->gr_name);
+                        if (!n)
+                                return -ENOMEM;
+
+                        r = hashmap_put(gid_by_name, n, GID_TO_PTR(gr->gr_gid));
+                        if (r == -EEXIST) {
+                                log_warning_errno(r, "Duplicate entry in %s for %s: %m", group_path, gr->gr_name);
+                                continue;
+                        }
+                        if (r < 0)
+                                return r;
+
+                        TAKE_PTR(n);
+                }
+
+                *cache = TAKE_PTR(gid_by_name);
+        }
+
+        found = hashmap_get(*cache, group);
+        if (!found)
+                return -ESRCH;
+
+        *ret_gid = PTR_TO_GID(found);
+        return 0;
+}
diff --git a/src/tmpfiles/offline-passwd.h b/src/tmpfiles/offline-passwd.h
new file mode 100644 (file)
index 0000000..90bdfc7
--- /dev/null
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <sys/types.h>
+
+#include "hashmap.h"
+
+int name_to_uid_offline(const char *root, const char *user, uid_t *ret_uid, Hashmap **cache);
+int name_to_gid_offline(const char *root, const char *group, gid_t *ret_gid, Hashmap **cache);
index 7137e9fbd725e758e7c7b615b06e0c0ff83171bf..37dde99ef0b058981bbba4badd01859188e54051 100644 (file)
@@ -39,6 +39,7 @@
 #include "main-func.h"
 #include "mkdir.h"
 #include "mountpoint-util.h"
+#include "offline-passwd.h"
 #include "pager.h"
 #include "parse-util.h"
 #include "path-lookup.h"
@@ -2487,7 +2488,63 @@ static int patch_var_run(const char *fname, unsigned line, char **path) {
         return 0;
 }
 
-static int parse_line(const char *fname, unsigned line, const char *buffer, bool *invalid_config) {
+static int find_uid(const char *user, uid_t *ret_uid, Hashmap **cache) {
+        int r;
+
+        assert(user);
+        assert(ret_uid);
+
+        /* First: parse as numeric UID string */
+        r = parse_uid(user, ret_uid);
+        if (r >= 0)
+                return r;
+
+        /* Second: pass to NSS if we are running "online" */
+        if (!arg_root)
+                return get_user_creds(&user, ret_uid, NULL, NULL, NULL, 0);
+
+        /* Third, synthesize "root" unconditionally */
+        if (streq(user, "root")) {
+                *ret_uid = 0;
+                return 0;
+        }
+
+        /* Fourth: use fgetpwent() to read /etc/passwd directly, if we are "offline" */
+        return name_to_uid_offline(arg_root, user, ret_uid, cache);
+}
+
+static int find_gid(const char *group, gid_t *ret_gid, Hashmap **cache) {
+        int r;
+
+        assert(group);
+        assert(ret_gid);
+
+        /* First: parse as numeric GID string */
+        r = parse_gid(group, ret_gid);
+        if (r >= 0)
+                return r;
+
+        /* Second: pass to NSS if we are running "online" */
+        if (!arg_root)
+                return get_group_creds(&group, ret_gid, 0);
+
+        /* Third, synthesize "root" unconditionally */
+        if (streq(group, "root")) {
+                *ret_gid = 0;
+                return 0;
+        }
+
+        /* Fourth: use fgetgrent() to read /etc/group directly, if we are "offline" */
+        return name_to_gid_offline(arg_root, group, ret_gid, cache);
+}
+
+static int parse_line(
+                const char *fname,
+                unsigned line,
+                const char *buffer,
+                bool *invalid_config,
+                Hashmap **uid_cache,
+                Hashmap **gid_cache) {
 
         _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
         _cleanup_(item_free_contents) Item i = {};
@@ -2718,9 +2775,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool
         }
 
         if (!empty_or_dash(user)) {
-                const char *u = user;
-
-                r = get_user_creds(&u, &i.uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING);
+                r = find_uid(user, &i.uid, uid_cache);
                 if (r < 0) {
                         *invalid_config = true;
                         return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve user '%s': %m", user);
@@ -2730,9 +2785,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool
         }
 
         if (!empty_or_dash(group)) {
-                const char *g = group;
-
-                r = get_group_creds(&g, &i.gid, USER_CREDS_ALLOW_MISSING);
+                r = find_gid(group, &i.gid, gid_cache);
                 if (r < 0) {
                         *invalid_config = true;
                         return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve group '%s'.", group);
@@ -2981,6 +3034,7 @@ static int parse_argv(int argc, char *argv[]) {
 }
 
 static int read_config_file(char **config_dirs, const char *fn, bool ignore_enoent, bool *invalid_config) {
+        _cleanup_(hashmap_freep) Hashmap *uid_cache = NULL, *gid_cache = NULL;
         _cleanup_fclose_ FILE *_f = NULL;
         Iterator iterator;
         unsigned v = 0;
@@ -3026,7 +3080,7 @@ static int read_config_file(char **config_dirs, const char *fn, bool ignore_enoe
                 if (IN_SET(*l, 0, '#'))
                         continue;
 
-                k = parse_line(fn, v, l, &invalid_line);
+                k = parse_line(fn, v, l, &invalid_line, &uid_cache, &gid_cache);
                 if (k < 0) {
                         if (invalid_line)
                                 /* Allow reporting with a special code if the caller requested this */