Merge pull request #15718 from poettering/tmpfiles-offline

tmpfiles: read /etc/passwd + /etc/group with fgetpwent()/fgetgrent() if --root= is specified
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2020-05-08 11:22:19 +02:00 committed by GitHub
commit 165bda37ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 206 additions and 17 deletions

View File

@ -161,10 +161,10 @@
<listitem><para>Takes a directory path as an argument. All paths will be prefixed with the given alternate
<replaceable>root</replaceable> path, including config search paths.</para>
<para>Note that this option does not alter how the users and groups specified in the configuration files are
resolved. With or without this option, users and groups are always resolved according to the host's user and
group databases, any such databases stored under the specified root directories are not
consulted.</para></listitem>
<para>When this option is used, the libc Name Service Switch (NSS) is bypassed for resolving users
and groups. Instead the files <filename>/etc/passwd</filename> and <filename>/etc/group</filename>
inside the alternate root are read directly. This means that users/groups not listed in these files
will not be resolved, i.e. LDAP NIS and other complex databases are not considered.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -2891,6 +2891,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],

View File

@ -1814,7 +1814,7 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_ROOT:
r = parse_path_argument_and_warn(optarg, true, &arg_root);
r = parse_path_argument_and_warn(optarg, /* suppress_root= */ false, &arg_root);
if (r < 0)
return r;
break;

View File

@ -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;
}

View File

@ -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);

View 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"
@ -2488,7 +2489,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 = {};
@ -2610,7 +2667,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool
case COPY_FILES:
if (!i.argument) {
i.argument = path_join(arg_root, "/usr/share/factory", i.path);
i.argument = path_join("/usr/share/factory", i.path);
if (!i.argument)
return log_oom();
@ -2618,7 +2675,9 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool
*invalid_config = true;
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Source path '%s' is not absolute.", i.argument);
} else if (arg_root) {
}
if (!empty_or_root(arg_root)) {
char *p;
p = path_join(arg_root, i.argument);
@ -2709,7 +2768,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool
return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to substitute specifiers in argument: %m");
}
if (arg_root) {
if (!empty_or_root(arg_root)) {
char *p;
p = path_join(arg_root, i.path);
@ -2719,9 +2778,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);
@ -2731,9 +2788,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);
@ -2941,7 +2996,7 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_ROOT:
r = parse_path_argument_and_warn(optarg, true, &arg_root);
r = parse_path_argument_and_warn(optarg, /* suppress_root= */ false, &arg_root);
if (r < 0)
return r;
break;
@ -2982,6 +3037,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;
@ -3027,7 +3083,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 */