diff --git a/Base/etc/shadow b/Base/etc/shadow new file mode 100644 index 00000000000..0c07ce03998 --- /dev/null +++ b/Base/etc/shadow @@ -0,0 +1 @@ +anon:$5$tuWhdzH7gPPKjddG$paaM44iScs1txck1SiKWMVdqL6r1Lc7qe5jciAR1NB0= diff --git a/Libraries/LibCore/Account.cpp b/Libraries/LibCore/Account.cpp index 928c6d26a91..7e4d40669a3 100644 --- a/Libraries/LibCore/Account.cpp +++ b/Libraries/LibCore/Account.cpp @@ -63,7 +63,36 @@ static Vector get_gids(const StringView& username) return extra_gids; } -Result Account::from_name(const char* username) +Result Account::from_passwd(const passwd& pwd, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file) +{ + RefPtr 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 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::from_name(const char* username, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file) { struct passwd* pwd = nullptr; errno = 0; @@ -74,13 +103,10 @@ Result Account::from_name(const char* username) return String(strerror(errno)); } - - Account account(pwd, get_gids(pwd->pw_name)); - endpwent(); - return account; + return from_passwd(*pwd, open_passwd_file, open_shadow_file); } -Result Account::from_uid(uid_t uid) +Result Account::from_uid(uid_t uid, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file) { struct passwd* pwd = nullptr; errno = 0; @@ -91,10 +117,7 @@ Result Account::from_uid(uid_t uid) return String(strerror(errno)); } - - Account account(pwd, get_gids(pwd->pw_name)); - endpwent(); - return account; + return from_passwd(*pwd, open_passwd_file, open_shadow_file); } bool Account::authenticate(const char* password) const @@ -144,21 +167,25 @@ void Account::delete_password() m_password_hash = ""; } -Account::Account(struct passwd* pwd, Vector extra_gids) - : m_username(pwd->pw_name) - , m_password_hash(pwd->pw_passwd) - , m_uid(pwd->pw_uid) - , m_gid(pwd->pw_gid) - , m_gecos(pwd->pw_gecos) - , m_home_directory(pwd->pw_dir) - , m_shell(pwd->pw_shell) +Account::Account(const passwd& pwd, Vector extra_gids, RefPtr passwd_file, RefPtr shadow_file) + : m_passwd_file(move(passwd_file)) + , m_shadow_file(move(shadow_file)) + , m_username(pwd.pw_name) + , m_uid(pwd.pw_uid) + , m_gid(pwd.pw_gid) + , m_gecos(pwd.pw_gecos) + , m_home_directory(pwd.pw_dir) + , m_shell(pwd.pw_shell) , 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(); @@ -166,42 +193,124 @@ bool Account::sync() errno = 0; while ((p = getpwent())) { if (p->pw_uid == m_uid) { - new_passwd_file.appendff("{}:{}:{}:{}:{}:{}:{}\n", + builder.appendff("{}:!:{}:{}:{}:{}:{}\n", m_username, - m_password_hash, m_uid, m_gid, m_gecos, m_home_directory, m_shell); } else { - new_passwd_file.appendff("{}:{}:{}:{}:{}:{}:{}\n", - p->pw_name, p->pw_passwd, p->pw_uid, + builder.appendff("{}:!:{}:{}:{}:{}:{}\n", + p->pw_name, p->pw_uid, p->pw_gid, p->pw_gecos, p->pw_dir, p->pw_shell); } } endpwent(); - if (errno) - return false; + if (errno) { + 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"); - if (!passwd_file) - return false; +void Account::load_shadow_file() +{ + ASSERT(m_shadow_file); + ASSERT(m_shadow_file->is_open()); - fwrite(contents.characters(), 1, contents.length(), passwd_file); - if (ferror(passwd_file)) { - int error = ferror(passwd_file); - fclose(passwd_file); - errno = error; + if (!m_shadow_file->seek(0)) { + ASSERT_NOT_REACHED(); + } + + Vector 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; } - fclose(passwd_file); return true; // FIXME: Sync extra groups. } + } diff --git a/Libraries/LibCore/Account.h b/Libraries/LibCore/Account.h index ce77a1e6569..fdd7d9bea93 100644 --- a/Libraries/LibCore/Account.h +++ b/Libraries/LibCore/Account.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -37,8 +38,20 @@ namespace Core { class Account { public: - static Result from_name(const char* username); - static Result from_uid(uid_t uid); + enum class OpenPasswdFile { + No, + ReadOnly, + ReadWrite, + }; + + enum class OpenShadowFile { + No, + ReadOnly, + ReadWrite, + }; + + static Result from_name(const char* username, OpenPasswdFile = OpenPasswdFile::No, OpenShadowFile = OpenShadowFile::No); + static Result from_uid(uid_t uid, OpenPasswdFile = OpenPasswdFile::No, OpenShadowFile = OpenShadowFile::No); bool authenticate(const char* password) const; bool login() const; @@ -63,7 +76,16 @@ public: bool sync(); private: - Account(struct passwd* pwd, Vector extra_gids); + static Result from_passwd(const passwd&, OpenPasswdFile, OpenShadowFile); + + Account(const passwd& pwd, Vector extra_gids, RefPtr passwd_file, RefPtr shadow_file); + void load_shadow_file(); + + String generate_passwd_file() const; + String generate_shadow_file() const; + + RefPtr m_passwd_file; + RefPtr m_shadow_file; String m_username; @@ -76,6 +98,12 @@ private: String m_home_directory; String m_shell; Vector m_extra_gids; + + struct ShadowEntry { + String username; + String password_hash; + }; + Vector m_shadow_entries; }; } diff --git a/Meta/build-root-filesystem.sh b/Meta/build-root-filesystem.sh index d5f8ff60c6f..75a62d5a850 100755 --- a/Meta/build-root-filesystem.sh +++ b/Meta/build-root-filesystem.sh @@ -62,6 +62,7 @@ chmod 4750 mnt/bin/shutdown chmod 4750 mnt/bin/keymap chown 0:$utmp_gid mnt/bin/utmpupdate chmod 2755 mnt/bin/utmpupdate +chmod 600 mnt/etc/shadow echo "done" diff --git a/Userland/passwd.cpp b/Userland/passwd.cpp index 1b9d4ab1a2f..e131081d3e2 100644 --- a/Userland/passwd.cpp +++ b/Userland/passwd.cpp @@ -54,6 +54,11 @@ int main(int argc, char** argv) return 1; } + if (unveil("/etc/shadow", "rwc") < 0) { + perror("unveil"); + return 1; + } + unveil(nullptr, nullptr); bool del = false; @@ -72,7 +77,9 @@ int main(int argc, char** argv) 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()) { fprintf(stderr, "Core::Account::%s: %s\n", (username) ? "from_name" : "from_uid", account_or_error.error().characters()); diff --git a/Userland/su.cpp b/Userland/su.cpp index fae1bb526d7..38d8b67ef11 100644 --- a/Userland/su.cpp +++ b/Userland/su.cpp @@ -50,7 +50,9 @@ int main(int argc, char** argv) if (geteuid() != 0) 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()) { fprintf(stderr, "Core::Account::from_name: %s\n", account_or_error.error().characters()); return 1;