mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-10 18:10:56 +09:00
LibCore+passwd+su+Base: Add /etc/shadow to hide hashes from users :^)
This patch moves the user account password hashes from /etc/passwd, where they were world-readable, to /etc/shadow, where only root can access them. The Core::Account class is extended to support both authentication against, and modification of /etc/shadow. The default password for "anon" as of this commit is "foo" :^)
This commit is contained in:
parent
c17056cf09
commit
9a688af4b1
Notes:
sideshowbarker
2024-07-18 23:59:40 +09:00
Author: https://github.com/awesomekling
Commit: 9a688af4b1
6 changed files with 189 additions and 41 deletions
1
Base/etc/shadow
Normal file
1
Base/etc/shadow
Normal file
|
@ -0,0 +1 @@
|
||||||
|
anon:$5$tuWhdzH7gPPKjddG$paaM44iScs1txck1SiKWMVdqL6r1Lc7qe5jciAR1NB0=
|
|
@ -63,7 +63,36 @@ static Vector<gid_t> get_gids(const StringView& username)
|
||||||
return extra_gids;
|
return extra_gids;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<Account, String> Account::from_name(const char* username)
|
Result<Account, String> Account::from_passwd(const passwd& pwd, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
|
||||||
|
{
|
||||||
|
RefPtr<Core::File> passwd_file;
|
||||||
|
if (open_passwd_file != Core::Account::OpenPasswdFile::No) {
|
||||||
|
auto open_mode = open_passwd_file == Core::Account::OpenPasswdFile::ReadOnly
|
||||||
|
? Core::File::OpenMode::ReadOnly
|
||||||
|
: Core::File::OpenMode::ReadWrite;
|
||||||
|
auto file_or_error = Core::File::open("/etc/passwd", open_mode);
|
||||||
|
if (file_or_error.is_error())
|
||||||
|
return file_or_error.error();
|
||||||
|
passwd_file = file_or_error.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
RefPtr<Core::File> shadow_file;
|
||||||
|
if (open_shadow_file != Core::Account::OpenShadowFile::No) {
|
||||||
|
auto open_mode = open_shadow_file == Core::Account::OpenShadowFile::ReadOnly
|
||||||
|
? Core::File::OpenMode::ReadOnly
|
||||||
|
: Core::File::OpenMode::ReadWrite;
|
||||||
|
auto file_or_error = Core::File::open("/etc/shadow", open_mode);
|
||||||
|
if (file_or_error.is_error())
|
||||||
|
return file_or_error.error();
|
||||||
|
shadow_file = file_or_error.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
Account account(pwd, get_gids(pwd.pw_name), move(passwd_file), move(shadow_file));
|
||||||
|
endpwent();
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<Account, String> Account::from_name(const char* username, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
|
||||||
{
|
{
|
||||||
struct passwd* pwd = nullptr;
|
struct passwd* pwd = nullptr;
|
||||||
errno = 0;
|
errno = 0;
|
||||||
|
@ -74,13 +103,10 @@ Result<Account, String> Account::from_name(const char* username)
|
||||||
|
|
||||||
return String(strerror(errno));
|
return String(strerror(errno));
|
||||||
}
|
}
|
||||||
|
return from_passwd(*pwd, open_passwd_file, open_shadow_file);
|
||||||
Account account(pwd, get_gids(pwd->pw_name));
|
|
||||||
endpwent();
|
|
||||||
return account;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<Account, String> Account::from_uid(uid_t uid)
|
Result<Account, String> Account::from_uid(uid_t uid, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
|
||||||
{
|
{
|
||||||
struct passwd* pwd = nullptr;
|
struct passwd* pwd = nullptr;
|
||||||
errno = 0;
|
errno = 0;
|
||||||
|
@ -91,10 +117,7 @@ Result<Account, String> Account::from_uid(uid_t uid)
|
||||||
|
|
||||||
return String(strerror(errno));
|
return String(strerror(errno));
|
||||||
}
|
}
|
||||||
|
return from_passwd(*pwd, open_passwd_file, open_shadow_file);
|
||||||
Account account(pwd, get_gids(pwd->pw_name));
|
|
||||||
endpwent();
|
|
||||||
return account;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Account::authenticate(const char* password) const
|
bool Account::authenticate(const char* password) const
|
||||||
|
@ -144,21 +167,25 @@ void Account::delete_password()
|
||||||
m_password_hash = "";
|
m_password_hash = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Account::Account(struct passwd* pwd, Vector<gid_t> extra_gids)
|
Account::Account(const passwd& pwd, Vector<gid_t> extra_gids, RefPtr<Core::File> passwd_file, RefPtr<Core::File> shadow_file)
|
||||||
: m_username(pwd->pw_name)
|
: m_passwd_file(move(passwd_file))
|
||||||
, m_password_hash(pwd->pw_passwd)
|
, m_shadow_file(move(shadow_file))
|
||||||
, m_uid(pwd->pw_uid)
|
, m_username(pwd.pw_name)
|
||||||
, m_gid(pwd->pw_gid)
|
, m_uid(pwd.pw_uid)
|
||||||
, m_gecos(pwd->pw_gecos)
|
, m_gid(pwd.pw_gid)
|
||||||
, m_home_directory(pwd->pw_dir)
|
, m_gecos(pwd.pw_gecos)
|
||||||
, m_shell(pwd->pw_shell)
|
, m_home_directory(pwd.pw_dir)
|
||||||
|
, m_shell(pwd.pw_shell)
|
||||||
, m_extra_gids(extra_gids)
|
, m_extra_gids(extra_gids)
|
||||||
{
|
{
|
||||||
|
if (m_shadow_file) {
|
||||||
|
load_shadow_file();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Account::sync()
|
String Account::generate_passwd_file() const
|
||||||
{
|
{
|
||||||
StringBuilder new_passwd_file;
|
StringBuilder builder;
|
||||||
|
|
||||||
setpwent();
|
setpwent();
|
||||||
|
|
||||||
|
@ -166,42 +193,124 @@ bool Account::sync()
|
||||||
errno = 0;
|
errno = 0;
|
||||||
while ((p = getpwent())) {
|
while ((p = getpwent())) {
|
||||||
if (p->pw_uid == m_uid) {
|
if (p->pw_uid == m_uid) {
|
||||||
new_passwd_file.appendff("{}:{}:{}:{}:{}:{}:{}\n",
|
builder.appendff("{}:!:{}:{}:{}:{}:{}\n",
|
||||||
m_username,
|
m_username,
|
||||||
m_password_hash,
|
|
||||||
m_uid, m_gid,
|
m_uid, m_gid,
|
||||||
m_gecos,
|
m_gecos,
|
||||||
m_home_directory,
|
m_home_directory,
|
||||||
m_shell);
|
m_shell);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
new_passwd_file.appendff("{}:{}:{}:{}:{}:{}:{}\n",
|
builder.appendff("{}:!:{}:{}:{}:{}:{}\n",
|
||||||
p->pw_name, p->pw_passwd, p->pw_uid,
|
p->pw_name, p->pw_uid,
|
||||||
p->pw_gid, p->pw_gecos, p->pw_dir,
|
p->pw_gid, p->pw_gecos, p->pw_dir,
|
||||||
p->pw_shell);
|
p->pw_shell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
endpwent();
|
endpwent();
|
||||||
|
|
||||||
if (errno)
|
if (errno) {
|
||||||
return false;
|
dbgln("errno was non-zero after generating new passwd file.");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
String contents = new_passwd_file.build();
|
return builder.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
FILE* passwd_file = fopen("/etc/passwd", "w");
|
void Account::load_shadow_file()
|
||||||
if (!passwd_file)
|
{
|
||||||
return false;
|
ASSERT(m_shadow_file);
|
||||||
|
ASSERT(m_shadow_file->is_open());
|
||||||
|
|
||||||
fwrite(contents.characters(), 1, contents.length(), passwd_file);
|
if (!m_shadow_file->seek(0)) {
|
||||||
if (ferror(passwd_file)) {
|
ASSERT_NOT_REACHED();
|
||||||
int error = ferror(passwd_file);
|
}
|
||||||
fclose(passwd_file);
|
|
||||||
errno = error;
|
Vector<ShadowEntry> entries;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
auto line = m_shadow_file->read_line();
|
||||||
|
if (line.is_null())
|
||||||
|
break;
|
||||||
|
auto parts = line.split(':');
|
||||||
|
if (parts.size() != 2) {
|
||||||
|
dbgln("Malformed shadow entry, ignoring.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto& username = parts[0];
|
||||||
|
const auto& password_hash = parts[1];
|
||||||
|
entries.append({ username, password_hash });
|
||||||
|
|
||||||
|
if (username == m_username) {
|
||||||
|
m_password_hash = password_hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_shadow_entries = move(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
String Account::generate_shadow_file() const
|
||||||
|
{
|
||||||
|
StringBuilder builder;
|
||||||
|
bool updated_entry_in_place = false;
|
||||||
|
for (auto& entry : m_shadow_entries) {
|
||||||
|
if (entry.username == m_username) {
|
||||||
|
updated_entry_in_place = true;
|
||||||
|
builder.appendff("{}:{}\n", m_username, m_password_hash);
|
||||||
|
} else {
|
||||||
|
builder.appendff("{}:{}\n", entry.username, entry.password_hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!updated_entry_in_place)
|
||||||
|
builder.appendff("{}:{}\n", m_username, m_password_hash);
|
||||||
|
return builder.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Account::sync()
|
||||||
|
{
|
||||||
|
ASSERT(m_passwd_file);
|
||||||
|
ASSERT(m_passwd_file->mode() == Core::File::OpenMode::ReadWrite);
|
||||||
|
ASSERT(m_shadow_file);
|
||||||
|
ASSERT(m_shadow_file->mode() == Core::File::OpenMode::ReadWrite);
|
||||||
|
|
||||||
|
// FIXME: Maybe reorganize this to create temporary files and finish it completely before renaming them to /etc/{passwd,shadow}
|
||||||
|
// If truncation succeeds but write fails, we'll have an empty file :(
|
||||||
|
|
||||||
|
auto new_passwd_file = generate_passwd_file();
|
||||||
|
auto new_shadow_file = generate_shadow_file();
|
||||||
|
|
||||||
|
if (new_passwd_file.is_null() || new_shadow_file.is_null()) {
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_passwd_file->seek(0) || !m_shadow_file->seek(0)) {
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_passwd_file->truncate(0)) {
|
||||||
|
dbgln("Truncating passwd file failed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_passwd_file->write(new_passwd_file)) {
|
||||||
|
// FIXME: Improve Core::File::write() error reporting.
|
||||||
|
dbgln("Writing to passwd file failed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_shadow_file->truncate(0)) {
|
||||||
|
dbgln("Truncating shadow file failed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_shadow_file->write(new_shadow_file)) {
|
||||||
|
// FIXME: Improve Core::File::write() error reporting.
|
||||||
|
dbgln("Writing to shadow file failed.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(passwd_file);
|
|
||||||
return true;
|
return true;
|
||||||
// FIXME: Sync extra groups.
|
// FIXME: Sync extra groups.
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <AK/Types.h>
|
#include <AK/Types.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
#include <LibCore/File.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
@ -37,8 +38,20 @@ namespace Core {
|
||||||
|
|
||||||
class Account {
|
class Account {
|
||||||
public:
|
public:
|
||||||
static Result<Account, String> from_name(const char* username);
|
enum class OpenPasswdFile {
|
||||||
static Result<Account, String> from_uid(uid_t uid);
|
No,
|
||||||
|
ReadOnly,
|
||||||
|
ReadWrite,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class OpenShadowFile {
|
||||||
|
No,
|
||||||
|
ReadOnly,
|
||||||
|
ReadWrite,
|
||||||
|
};
|
||||||
|
|
||||||
|
static Result<Account, String> from_name(const char* username, OpenPasswdFile = OpenPasswdFile::No, OpenShadowFile = OpenShadowFile::No);
|
||||||
|
static Result<Account, String> from_uid(uid_t uid, OpenPasswdFile = OpenPasswdFile::No, OpenShadowFile = OpenShadowFile::No);
|
||||||
|
|
||||||
bool authenticate(const char* password) const;
|
bool authenticate(const char* password) const;
|
||||||
bool login() const;
|
bool login() const;
|
||||||
|
@ -63,7 +76,16 @@ public:
|
||||||
bool sync();
|
bool sync();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Account(struct passwd* pwd, Vector<gid_t> extra_gids);
|
static Result<Account, String> from_passwd(const passwd&, OpenPasswdFile, OpenShadowFile);
|
||||||
|
|
||||||
|
Account(const passwd& pwd, Vector<gid_t> extra_gids, RefPtr<Core::File> passwd_file, RefPtr<Core::File> shadow_file);
|
||||||
|
void load_shadow_file();
|
||||||
|
|
||||||
|
String generate_passwd_file() const;
|
||||||
|
String generate_shadow_file() const;
|
||||||
|
|
||||||
|
RefPtr<Core::File> m_passwd_file;
|
||||||
|
RefPtr<Core::File> m_shadow_file;
|
||||||
|
|
||||||
String m_username;
|
String m_username;
|
||||||
|
|
||||||
|
@ -76,6 +98,12 @@ private:
|
||||||
String m_home_directory;
|
String m_home_directory;
|
||||||
String m_shell;
|
String m_shell;
|
||||||
Vector<gid_t> m_extra_gids;
|
Vector<gid_t> m_extra_gids;
|
||||||
|
|
||||||
|
struct ShadowEntry {
|
||||||
|
String username;
|
||||||
|
String password_hash;
|
||||||
|
};
|
||||||
|
Vector<ShadowEntry> m_shadow_entries;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ chmod 4750 mnt/bin/shutdown
|
||||||
chmod 4750 mnt/bin/keymap
|
chmod 4750 mnt/bin/keymap
|
||||||
chown 0:$utmp_gid mnt/bin/utmpupdate
|
chown 0:$utmp_gid mnt/bin/utmpupdate
|
||||||
chmod 2755 mnt/bin/utmpupdate
|
chmod 2755 mnt/bin/utmpupdate
|
||||||
|
chmod 600 mnt/etc/shadow
|
||||||
|
|
||||||
echo "done"
|
echo "done"
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,11 @@ int main(int argc, char** argv)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (unveil("/etc/shadow", "rwc") < 0) {
|
||||||
|
perror("unveil");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
unveil(nullptr, nullptr);
|
unveil(nullptr, nullptr);
|
||||||
|
|
||||||
bool del = false;
|
bool del = false;
|
||||||
|
@ -72,7 +77,9 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
uid_t current_uid = getuid();
|
uid_t current_uid = getuid();
|
||||||
|
|
||||||
auto account_or_error = (username) ? Core::Account::from_name(username) : Core::Account::from_uid(current_uid);
|
auto account_or_error = (username)
|
||||||
|
? Core::Account::from_name(username, Core::Account::OpenPasswdFile::ReadWrite, Core::Account::OpenShadowFile::ReadWrite)
|
||||||
|
: Core::Account::from_uid(current_uid, Core::Account::OpenPasswdFile::ReadWrite, Core::Account::OpenShadowFile::ReadWrite);
|
||||||
|
|
||||||
if (account_or_error.is_error()) {
|
if (account_or_error.is_error()) {
|
||||||
fprintf(stderr, "Core::Account::%s: %s\n", (username) ? "from_name" : "from_uid", account_or_error.error().characters());
|
fprintf(stderr, "Core::Account::%s: %s\n", (username) ? "from_name" : "from_uid", account_or_error.error().characters());
|
||||||
|
|
|
@ -50,7 +50,9 @@ int main(int argc, char** argv)
|
||||||
if (geteuid() != 0)
|
if (geteuid() != 0)
|
||||||
fprintf(stderr, "Not running as root :(\n");
|
fprintf(stderr, "Not running as root :(\n");
|
||||||
|
|
||||||
auto account_or_error = (user) ? Core::Account::from_name(user) : Core::Account::from_uid(0);
|
auto account_or_error = (user)
|
||||||
|
? Core::Account::from_name(user, Core::Account::OpenPasswdFile::No, Core::Account::OpenShadowFile::ReadOnly)
|
||||||
|
: Core::Account::from_uid(0, Core::Account::OpenPasswdFile::No, Core::Account::OpenShadowFile::ReadOnly);
|
||||||
if (account_or_error.is_error()) {
|
if (account_or_error.is_error()) {
|
||||||
fprintf(stderr, "Core::Account::from_name: %s\n", account_or_error.error().characters());
|
fprintf(stderr, "Core::Account::from_name: %s\n", account_or_error.error().characters());
|
||||||
return 1;
|
return 1;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue