Files
slocker-lite/src/user_spec.cpp
T
ceamac 355675b43f Resolve HOME to the target user's home directory instead of /root
ResolvedUser now carries home, looked up from the image's own
/etc/passwd entry for the final resolved uid (falling back to /root
for uid 0 or / otherwise when there's no matching row). bwrap's HOME
now uses this whenever a user override applies (--user/--group or an
image-declared default user); the plain /root default is kept only
when no override applies at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-08-22 06:47:16 +00:00

134 lines
4.6 KiB
C++

// Copyright (C) 2026 Viorel Munteanu
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "user_spec.h"
#include <algorithm>
#include <cctype>
#include <fstream>
#include <sstream>
#include <spdlog/spdlog.h>
namespace {
std::vector<std::string> split(const std::string& line, char delim) {
std::vector<std::string> fields;
std::istringstream stream(line);
std::string field;
while (std::getline(stream, field, delim)) {
fields.push_back(field);
}
return fields;
}
bool is_all_digits(const std::string& s) {
return !s.empty() && std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isdigit(c); });
}
std::optional<int> parse_int(const std::string& s) {
try {
return std::stoi(s);
} catch (const std::exception&) {
return std::nullopt;
}
}
// Looks up `key` by name (field 0) or numeric id (field `id_field`) in a
// colon-separated database file (/etc/passwd or /etc/group). Malformed lines are
// skipped rather than treated as errors.
std::optional<std::vector<std::string>> lookup_entry(const std::filesystem::path& db_file,
const std::string& key, size_t id_field) {
std::ifstream in(db_file, std::ios::binary);
if (!in) {
return std::nullopt;
}
std::string line;
while (std::getline(in, line)) {
auto fields = split(line, ':');
if (fields.size() <= id_field) {
continue;
}
if (fields[0] == key || fields[id_field] == key) {
return fields;
}
}
return std::nullopt;
}
} // namespace
std::optional<ResolvedUser> resolve_user_and_group(const std::string& user,
const std::optional<std::string>& group,
const std::filesystem::path& image_root) {
std::filesystem::path passwd_file = image_root / "etc" / "passwd";
int uid = 0;
int gid = 0;
if (is_all_digits(user)) {
uid = *parse_int(user);
auto entry = lookup_entry(passwd_file, user, 2);
if (entry && entry->size() > 3) {
gid = parse_int((*entry)[3]).value_or(uid);
} else {
gid = uid;
}
} else {
auto entry = lookup_entry(passwd_file, user, 2);
if (!entry || entry->size() <= 3) {
spdlog::error("could not resolve user '{}' in the image's /etc/passwd", user);
return std::nullopt;
}
auto entry_uid = parse_int((*entry)[2]);
auto entry_gid = parse_int((*entry)[3]);
if (!entry_uid || !entry_gid) {
spdlog::error("malformed /etc/passwd entry for user '{}'", user);
return std::nullopt;
}
uid = *entry_uid;
gid = *entry_gid;
}
if (group) {
if (is_all_digits(*group)) {
gid = *parse_int(*group);
} else {
std::filesystem::path group_file = image_root / "etc" / "group";
auto entry = lookup_entry(group_file, *group, 2);
auto entry_gid = entry ? parse_int((*entry)[2]) : std::nullopt;
if (!entry_gid) {
spdlog::error("could not resolve group '{}' in the image's /etc/group", *group);
return std::nullopt;
}
gid = *entry_gid;
}
}
// Looked up by the final resolved uid (field 2), independent of whether `user`
// was given as a name or a number, so it matches whichever /etc/passwd row
// actually owns that uid. No matching row -> fall back to "/root" for uid 0
// (matches useradd-less images' own convention for root) or "/" otherwise.
std::string home = uid == 0 ? "/root" : "/";
auto home_entry = lookup_entry(passwd_file, std::to_string(uid), 2);
if (home_entry && home_entry->size() > 5 && !(*home_entry)[5].empty()) {
home = (*home_entry)[5];
}
return ResolvedUser{uid, gid, home};
}