LibCore: Remove unused classes and headers

This commit is contained in:
rmg-x 2024-10-04 17:15:53 -05:00 committed by Andreas Kling
parent 8edaec79de
commit d8c36ed458
Notes: github-actions[bot] 2024-10-05 07:21:53 +00:00
20 changed files with 0 additions and 1846 deletions

View file

@ -44,7 +44,6 @@ source_set("sources") {
"ConfigFile.h",
"DateTime.cpp",
"DateTime.h",
"Debounce.h",
"DeferredInvocationContext.h",
"ElapsedTimer.cpp",
"ElapsedTimer.h",
@ -58,8 +57,6 @@ source_set("sources") {
"EventLoopImplementationUnix.h",
"EventReceiver.cpp",
"EventReceiver.h",
"LockFile.cpp",
"LockFile.h",
"MappedFile.cpp",
"MappedFile.h",
"MimeData.cpp",
@ -71,18 +68,12 @@ source_set("sources") {
"Platform/ProcessStatistics.h",
"Process.cpp",
"Process.h",
"ProcessStatisticsReader.cpp",
"ProcessStatisticsReader.h",
"Promise.h",
"Proxy.h",
"Resource.cpp",
"Resource.h",
"ResourceImplementation.cpp",
"ResourceImplementationFile.cpp",
"SOCKSProxyClient.cpp",
"SOCKSProxyClient.h",
"SecretString.cpp",
"SecretString.h",
"SessionManagement.cpp",
"SessionManagement.h",
"SharedCircularQueue.h",
@ -100,18 +91,9 @@ source_set("sources") {
"Timer.h",
"UDPServer.cpp",
"UDPServer.h",
"UmaskScope.h",
]
if (current_os != "android") {
sources += [
"Account.cpp",
"Account.h",
"FilePermissionsMask.cpp",
"FilePermissionsMask.h",
"GetPassword.cpp",
"GetPassword.h",
"Group.cpp",
"Group.h",
"LocalServer.cpp",
"LocalServer.h",
]

View file

@ -1,383 +0,0 @@
/*
* Copyright (c) 2020, Peter Elliott <pelliott@serenityos.org>
* Copyright (c) 2021-2022, Brian Gianforcaro <bgianf@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Base64.h>
#include <AK/Memory.h>
#include <AK/Random.h>
#include <AK/ScopeGuard.h>
#include <LibCore/Account.h>
#include <LibCore/Directory.h>
#include <LibCore/System.h>
#include <LibCore/UmaskScope.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_HAIKU)
# include <crypt.h>
# include <shadow.h>
#endif
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
namespace Core {
static ByteString get_salt()
{
char random_data[12];
fill_with_random({ random_data, sizeof(random_data) });
StringBuilder builder;
builder.append("$5$"sv);
// FIXME: change to TRY() and make method fallible
auto salt_string = MUST(encode_base64({ random_data, sizeof(random_data) }));
builder.append(salt_string);
return builder.to_byte_string();
}
static Vector<gid_t> get_extra_gids(passwd const& pwd)
{
StringView username { pwd.pw_name, strlen(pwd.pw_name) };
Vector<gid_t> extra_gids;
setgrent();
for (auto* group = getgrent(); group; group = getgrent()) {
if (group->gr_gid == pwd.pw_gid)
continue;
for (size_t i = 0; group->gr_mem[i]; ++i) {
if (username == group->gr_mem[i]) {
extra_gids.append(group->gr_gid);
break;
}
}
}
endgrent();
return extra_gids;
}
ErrorOr<Account> Account::from_passwd(passwd const& pwd, spwd const& spwd)
{
Account account(pwd, spwd, get_extra_gids(pwd));
endpwent();
#ifndef AK_OS_BSD_GENERIC
endspent();
#endif
return account;
}
ErrorOr<Account> Account::self([[maybe_unused]] Read options)
{
Vector<gid_t> extra_gids = TRY(Core::System::getgroups());
auto pwd = TRY(Core::System::getpwuid(getuid()));
if (!pwd.has_value())
return Error::from_string_literal("No such user");
spwd spwd = {};
#ifndef AK_OS_BSD_GENERIC
if (options != Read::PasswdOnly) {
auto maybe_spwd = TRY(Core::System::getspnam({ pwd->pw_name, strlen(pwd->pw_name) }));
if (!maybe_spwd.has_value())
return Error::from_string_literal("No shadow entry for user");
spwd = maybe_spwd.release_value();
}
#endif
return Account(*pwd, spwd, extra_gids);
}
ErrorOr<Account> Account::from_name(StringView username, [[maybe_unused]] Read options)
{
auto pwd = TRY(Core::System::getpwnam(username));
if (!pwd.has_value())
return Error::from_string_literal("No such user");
spwd spwd = {};
#ifndef AK_OS_BSD_GENERIC
if (options != Read::PasswdOnly) {
auto maybe_spwd = TRY(Core::System::getspnam({ pwd->pw_name, strlen(pwd->pw_name) }));
if (!maybe_spwd.has_value())
return Error::from_string_literal("No shadow entry for user");
spwd = maybe_spwd.release_value();
}
#endif
return from_passwd(*pwd, spwd);
}
ErrorOr<Account> Account::from_uid(uid_t uid, [[maybe_unused]] Read options)
{
auto pwd = TRY(Core::System::getpwuid(uid));
if (!pwd.has_value())
return Error::from_string_literal("No such user");
spwd spwd = {};
#ifndef AK_OS_BSD_GENERIC
if (options != Read::PasswdOnly) {
auto maybe_spwd = TRY(Core::System::getspnam({ pwd->pw_name, strlen(pwd->pw_name) }));
if (!maybe_spwd.has_value())
return Error::from_string_literal("No shadow entry for user");
spwd = maybe_spwd.release_value();
}
#endif
return from_passwd(*pwd, spwd);
}
ErrorOr<Vector<Account>> Account::all([[maybe_unused]] Read options)
{
Vector<Account> accounts;
char buffer[1024] = { 0 };
ScopeGuard pwent_guard([] { endpwent(); });
setpwent();
while (true) {
auto pwd = TRY(Core::System::getpwent({ buffer, sizeof(buffer) }));
if (!pwd.has_value())
break;
spwd spwd = {};
#ifndef AK_OS_BSD_GENERIC
ScopeGuard spent_guard([] { endspent(); });
if (options != Read::PasswdOnly) {
auto maybe_spwd = TRY(Core::System::getspnam({ pwd->pw_name, strlen(pwd->pw_name) }));
if (!maybe_spwd.has_value())
return Error::from_string_literal("No shadow entry for user");
spwd = maybe_spwd.release_value();
}
#endif
accounts.append({ *pwd, spwd, get_extra_gids(*pwd) });
}
return accounts;
}
bool Account::authenticate(SecretString const& password) const
{
// If there was no shadow entry for this account, authentication always fails.
if (!m_password_hash.has_value())
return false;
// An empty passwd field indicates that no password is required to log in.
if (m_password_hash->is_empty())
return true;
// FIXME: Use crypt_r if it can be built in lagom.
auto const bytes = m_password_hash->characters();
char* hash = crypt(password.characters(), bytes);
return hash != nullptr && AK::timing_safe_compare(hash, bytes, m_password_hash->length());
}
ErrorOr<void> Account::login() const
{
TRY(Core::System::setgroups(m_extra_gids));
TRY(Core::System::setgid(m_gid));
TRY(Core::System::setuid(m_uid));
return {};
}
void Account::set_password(SecretString const& password)
{
m_password_hash = crypt(password.characters(), get_salt().characters());
}
void Account::set_password_enabled(bool enabled)
{
auto flattened_password_hash = m_password_hash.value_or(ByteString::empty());
if (enabled && !flattened_password_hash.is_empty() && flattened_password_hash[0] == '!') {
m_password_hash = flattened_password_hash.substring(1, flattened_password_hash.length() - 1);
} else if (!enabled && (flattened_password_hash.is_empty() || flattened_password_hash[0] != '!')) {
StringBuilder builder;
builder.append('!');
builder.append(flattened_password_hash);
m_password_hash = builder.to_byte_string();
}
}
void Account::delete_password()
{
m_password_hash = ByteString::empty();
}
Account::Account(passwd const& pwd, spwd const& spwd, Vector<gid_t> extra_gids)
: m_username(pwd.pw_name)
, m_password_hash(spwd.sp_pwdp ? Optional<ByteString>(spwd.sp_pwdp) : OptionalNone {})
, 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(move(extra_gids))
{
}
ErrorOr<ByteString> Account::generate_passwd_file() const
{
StringBuilder builder;
char buffer[1024] = { 0 };
ScopeGuard pwent_guard([] { endpwent(); });
setpwent();
while (true) {
auto pwd = TRY(Core::System::getpwent({ buffer, sizeof(buffer) }));
if (!pwd.has_value())
break;
if (pwd->pw_name == m_username) {
if (m_deleted)
continue;
builder.appendff("{}:!:{}:{}:{}:{}:{}\n",
m_username,
m_uid, m_gid,
m_gecos,
m_home_directory,
m_shell);
} else {
builder.appendff("{}:!:{}:{}:{}:{}:{}\n",
pwd->pw_name, pwd->pw_uid,
pwd->pw_gid, pwd->pw_gecos, pwd->pw_dir,
pwd->pw_shell);
}
}
return builder.to_byte_string();
}
ErrorOr<ByteString> Account::generate_group_file() const
{
StringBuilder builder;
char buffer[1024] = { 0 };
ScopeGuard pwent_guard([] { endgrent(); });
setgrent();
while (true) {
auto group = TRY(Core::System::getgrent(buffer));
if (!group.has_value())
break;
auto should_be_present = !m_deleted && m_extra_gids.contains_slow(group->gr_gid);
auto already_present = false;
Vector<char const*> members;
for (size_t i = 0; group->gr_mem[i]; ++i) {
auto const* member = group->gr_mem[i];
if (member == m_username) {
already_present = true;
if (!should_be_present)
continue;
}
members.append(member);
}
if (should_be_present && !already_present)
members.append(m_username.characters());
builder.appendff("{}:{}:{}:{}\n", group->gr_name, group->gr_passwd, group->gr_gid, ByteString::join(","sv, members));
}
return builder.to_byte_string();
}
#ifndef AK_OS_BSD_GENERIC
ErrorOr<ByteString> Account::generate_shadow_file() const
{
StringBuilder builder;
setspent();
struct spwd* p;
errno = 0;
while ((p = getspent())) {
if (p->sp_namp == m_username) {
if (m_deleted)
continue;
builder.appendff("{}:{}", m_username, m_password_hash.value_or(ByteString::empty()));
} else
builder.appendff("{}:{}", p->sp_namp, p->sp_pwdp);
builder.appendff(":{}:{}:{}:{}:{}:{}:{}\n",
(p->sp_lstchg == -1) ? "" : ByteString::formatted("{}", p->sp_lstchg),
(p->sp_min == -1) ? "" : ByteString::formatted("{}", p->sp_min),
(p->sp_max == -1) ? "" : ByteString::formatted("{}", p->sp_max),
(p->sp_warn == -1) ? "" : ByteString::formatted("{}", p->sp_warn),
(p->sp_inact == -1) ? "" : ByteString::formatted("{}", p->sp_inact),
(p->sp_expire == -1) ? "" : ByteString::formatted("{}", p->sp_expire),
(p->sp_flag == 0) ? "" : ByteString::formatted("{}", p->sp_flag));
}
endspent();
if (errno)
return Error::from_errno(errno);
return builder.to_byte_string();
}
#endif
ErrorOr<void> Account::sync()
{
Core::UmaskScope umask_scope(0777);
auto new_passwd_file_content = TRY(generate_passwd_file());
auto new_group_file_content = TRY(generate_group_file());
#ifndef AK_OS_BSD_GENERIC
auto new_shadow_file_content = TRY(generate_shadow_file());
#endif
char new_passwd_file[] = "/etc/passwd.XXXXXX";
char new_group_file[] = "/etc/group.XXXXXX";
#ifndef AK_OS_BSD_GENERIC
char new_shadow_file[] = "/etc/shadow.XXXXXX";
#endif
{
auto new_passwd_fd = TRY(Core::System::mkstemp(new_passwd_file));
ScopeGuard new_passwd_fd_guard = [new_passwd_fd] { close(new_passwd_fd); };
TRY(Core::System::fchmod(new_passwd_fd, 0644));
auto new_group_fd = TRY(Core::System::mkstemp(new_group_file));
ScopeGuard new_group_fd_guard = [new_group_fd] { close(new_group_fd); };
TRY(Core::System::fchmod(new_group_fd, 0644));
#ifndef AK_OS_BSD_GENERIC
auto new_shadow_fd = TRY(Core::System::mkstemp(new_shadow_file));
ScopeGuard new_shadow_fd_guard = [new_shadow_fd] { close(new_shadow_fd); };
TRY(Core::System::fchmod(new_shadow_fd, 0600));
#endif
auto nwritten = TRY(Core::System::write(new_passwd_fd, new_passwd_file_content.bytes()));
VERIFY(static_cast<size_t>(nwritten) == new_passwd_file_content.length());
nwritten = TRY(Core::System::write(new_group_fd, new_group_file_content.bytes()));
VERIFY(static_cast<size_t>(nwritten) == new_group_file_content.length());
#ifndef AK_OS_BSD_GENERIC
nwritten = TRY(Core::System::write(new_shadow_fd, new_shadow_file_content.bytes()));
VERIFY(static_cast<size_t>(nwritten) == new_shadow_file_content.length());
#endif
}
auto new_passwd_file_view = StringView { new_passwd_file, sizeof(new_passwd_file) };
TRY(Core::System::rename(new_passwd_file_view, "/etc/passwd"sv));
auto new_group_file_view = StringView { new_group_file, sizeof(new_group_file) };
TRY(Core::System::rename(new_group_file_view, "/etc/group"sv));
#ifndef AK_OS_BSD_GENERIC
auto new_shadow_file_view = StringView { new_shadow_file, sizeof(new_shadow_file) };
TRY(Core::System::rename(new_shadow_file_view, "/etc/shadow"sv));
#endif
return {};
}
}

View file

@ -1,95 +0,0 @@
/*
* Copyright (c) 2020, Peter Elliott <pelliott@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/Types.h>
#include <AK/Vector.h>
#include <LibCore/SecretString.h>
#include <pwd.h>
#ifndef AK_OS_BSD_GENERIC
# include <shadow.h>
#endif
#include <sys/types.h>
namespace Core {
#ifdef AK_OS_BSD_GENERIC
struct spwd {
char* sp_namp;
char* sp_pwdp;
};
#endif
class Account {
public:
enum class Read {
All,
PasswdOnly
};
static ErrorOr<Account> self(Read options = Read::All);
static ErrorOr<Account> from_name(StringView username, Read options = Read::All);
static ErrorOr<Account> from_uid(uid_t uid, Read options = Read::All);
static ErrorOr<Vector<Account>> all(Read options = Read::All);
bool authenticate(SecretString const& password) const;
ErrorOr<void> login() const;
ByteString username() const { return m_username; }
ByteString password_hash() const { return m_password_hash.value_or(ByteString::empty()); }
// Setters only affect in-memory copy of password.
// You must call sync to apply changes.
void set_password(SecretString const& password);
void set_password_enabled(bool enabled);
void set_home_directory(StringView home_directory) { m_home_directory = home_directory; }
void set_uid(uid_t uid) { m_uid = uid; }
void set_gid(gid_t gid) { m_gid = gid; }
void set_shell(StringView shell) { m_shell = shell; }
void set_gecos(StringView gecos) { m_gecos = gecos; }
void set_deleted() { m_deleted = true; }
void set_extra_gids(Vector<gid_t> extra_gids) { m_extra_gids = move(extra_gids); }
void delete_password();
// A nonexistent password means that this account was missing from /etc/shadow.
// It's considered to have a password in that case, and authentication will always fail.
bool has_password() const { return !m_password_hash.has_value() || !m_password_hash->is_empty(); }
uid_t uid() const { return m_uid; }
gid_t gid() const { return m_gid; }
ByteString const& gecos() const { return m_gecos; }
ByteString const& home_directory() const { return m_home_directory; }
ByteString const& shell() const { return m_shell; }
Vector<gid_t> const& extra_gids() const { return m_extra_gids; }
ErrorOr<void> sync();
private:
static ErrorOr<Account> from_passwd(passwd const&, spwd const&);
Account(passwd const& pwd, spwd const& spwd, Vector<gid_t> extra_gids);
ErrorOr<ByteString> generate_passwd_file() const;
ErrorOr<ByteString> generate_group_file() const;
#ifndef AK_OS_BSD_GENERIC
ErrorOr<ByteString> generate_shadow_file() const;
#endif
ByteString m_username;
Optional<ByteString> m_password_hash;
uid_t m_uid { 0 };
gid_t m_gid { 0 };
ByteString m_gecos;
ByteString m_home_directory;
ByteString m_shell;
Vector<gid_t> m_extra_gids;
bool m_deleted { false };
};
}

View file

@ -31,33 +31,21 @@ set(SOURCES
EventLoopImplementation.cpp
EventLoopImplementationUnix.cpp
EventReceiver.cpp
LockFile.cpp
MappedFile.cpp
MimeData.cpp
Notifier.cpp
Process.cpp
ProcessStatisticsReader.cpp
Resource.cpp
ResourceImplementation.cpp
ResourceImplementationFile.cpp
SecretString.cpp
SessionManagement.cpp
Socket.cpp
SOCKSProxyClient.cpp
SystemServerTakeover.cpp
TCPServer.cpp
ThreadEventQueue.cpp
Timer.cpp
UDPServer.cpp
)
if (NOT ANDROID AND NOT WIN32 AND NOT EMSCRIPTEN)
list(APPEND SOURCES
Account.cpp
FilePermissionsMask.cpp
GetPassword.cpp
Group.cpp
)
endif()
if (NOT WIN32 AND NOT EMSCRIPTEN)
list(APPEND SOURCES LocalServer.cpp)
endif()

View file

@ -1,29 +0,0 @@
/*
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibCore/Timer.h>
namespace Core {
template<typename TFunction>
auto debounce(int timeout, TFunction function)
{
RefPtr<Core::Timer> timer;
return [=]<typename... T>(T... args) mutable {
auto apply_function = [=] { function(args...); };
if (timer) {
timer->stop();
timer->on_timeout = move(apply_function);
} else {
timer = Core::Timer::create_single_shot(timeout, move(apply_function));
}
timer->start();
};
}
}

View file

@ -1,176 +0,0 @@
/*
* Copyright (c) 2021, Xavier Defrang <xavier.defrang@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/CharacterTypes.h>
#include <AK/StringUtils.h>
#include <LibCore/FilePermissionsMask.h>
namespace Core {
enum State {
Classes,
Mode
};
enum ClassFlag {
Other = 1,
Group = 2,
User = 4,
All = 7
};
enum Operation {
Add,
Remove,
Assign,
};
ErrorOr<FilePermissionsMask> FilePermissionsMask::parse(StringView string)
{
return (!string.is_empty() && is_ascii_digit(string[0]))
? from_numeric_notation(string)
: from_symbolic_notation(string);
}
ErrorOr<FilePermissionsMask> FilePermissionsMask::from_numeric_notation(StringView string)
{
string = string.trim_whitespace();
mode_t mode = AK::StringUtils::convert_to_uint_from_octal<u16>(string, TrimWhitespace::No).value_or(010000);
if (mode > 07777)
return Error::from_string_literal("invalid octal representation");
FilePermissionsMask mask;
mask.assign_permissions(mode);
// For compatibility purposes, just clear the special mode bits if we explicitly passed a 4-character mode.
if (string.length() >= 4)
mask.remove_permissions(07000);
return mask;
}
ErrorOr<FilePermissionsMask> FilePermissionsMask::from_symbolic_notation(StringView string)
{
auto mask = FilePermissionsMask();
u8 state = State::Classes;
u8 classes = 0;
u8 operation = 0;
for (auto ch : string) {
switch (state) {
case State::Classes: {
// zero or more [ugoa] terminated by one operator [+-=]
if (ch == 'u')
classes |= ClassFlag::User;
else if (ch == 'g')
classes |= ClassFlag::Group;
else if (ch == 'o')
classes |= ClassFlag::Other;
else if (ch == 'a')
classes = ClassFlag::All;
else {
if (ch == '+')
operation = Operation::Add;
else if (ch == '-')
operation = Operation::Remove;
else if (ch == '=')
operation = Operation::Assign;
else if (classes == 0)
return Error::from_string_literal("invalid class: expected 'u', 'g', 'o' or 'a'");
else
return Error::from_string_literal("invalid operation: expected '+', '-' or '='");
// if an operation was specified without a class, assume all
if (classes == 0)
classes = ClassFlag::All;
state = State::Mode;
}
break;
}
case State::Mode: {
// one or more [rwx] terminated by a comma
// End of mode part, expect class next
if (ch == ',') {
state = State::Classes;
classes = operation = 0;
continue;
}
mode_t write_bits = 0;
bool apply_to_directories_and_executables_only = false;
switch (ch) {
case 'r':
write_bits = 4;
break;
case 'w':
write_bits = 2;
break;
case 'x':
write_bits = 1;
break;
case 'X':
write_bits = 1;
apply_to_directories_and_executables_only = true;
break;
default:
return Error::from_string_literal("invalid symbolic permission: expected 'r', 'w' or 'x'");
}
mode_t clear_bits = operation == Operation::Assign ? 7 : write_bits;
FilePermissionsMask& edit_mask = apply_to_directories_and_executables_only ? mask.directory_or_executable_mask() : mask;
// Update masks one class at a time in other, group, user order
for (auto cls = classes; cls != 0; cls >>= 1) {
if (cls & 1) {
if (operation == Operation::Add || operation == Operation::Assign)
edit_mask.add_permissions(write_bits);
if (operation == Operation::Remove || operation == Operation::Assign)
edit_mask.remove_permissions(clear_bits);
}
write_bits <<= 3;
clear_bits <<= 3;
}
break;
}
default:
VERIFY_NOT_REACHED();
}
}
return mask;
}
FilePermissionsMask& FilePermissionsMask::assign_permissions(mode_t mode)
{
m_write_mask = mode;
m_clear_mask = 0777;
return *this;
}
FilePermissionsMask& FilePermissionsMask::add_permissions(mode_t mode)
{
m_write_mask |= mode;
return *this;
}
FilePermissionsMask& FilePermissionsMask::remove_permissions(mode_t mode)
{
m_clear_mask |= mode;
return *this;
}
}

View file

@ -1,57 +0,0 @@
/*
* Copyright (c) 2021, Xavier Defrang <xavier.defrang@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <AK/OwnPtr.h>
#include <sys/stat.h>
namespace Core {
class FilePermissionsMask {
public:
static ErrorOr<FilePermissionsMask> parse(StringView string);
static ErrorOr<FilePermissionsMask> from_numeric_notation(StringView string);
static ErrorOr<FilePermissionsMask> from_symbolic_notation(StringView string);
FilePermissionsMask()
: m_clear_mask(0)
, m_write_mask(0)
{
}
FilePermissionsMask& assign_permissions(mode_t mode);
FilePermissionsMask& add_permissions(mode_t mode);
FilePermissionsMask& remove_permissions(mode_t mode);
mode_t apply(mode_t mode) const
{
if (m_directory_or_executable_mask && (S_ISDIR(mode) || (mode & 0111) != 0))
mode = m_directory_or_executable_mask->apply(mode);
return m_write_mask | (mode & ~m_clear_mask);
}
mode_t clear_mask() const { return m_clear_mask; }
mode_t write_mask() const { return m_write_mask; }
FilePermissionsMask& directory_or_executable_mask()
{
if (!m_directory_or_executable_mask)
m_directory_or_executable_mask = make<FilePermissionsMask>();
return *m_directory_or_executable_mask;
}
private:
mode_t m_clear_mask; // the bits that will be cleared
mode_t m_write_mask; // the bits that will be set
// A separate mask, only for files that already have some executable bit set or directories.
OwnPtr<FilePermissionsMask> m_directory_or_executable_mask;
};
}

View file

@ -1,45 +0,0 @@
/*
* Copyright (c) 2020, Peter Elliott <pelliott@serenityos.org>
* Copyright (c) 2021, Emanuele Torre <torreemanuele6@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/GetPassword.h>
#include <LibCore/System.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
namespace Core {
ErrorOr<SecretString> get_password(StringView prompt)
{
TRY(Core::System::write(STDOUT_FILENO, prompt.bytes()));
auto original = TRY(Core::System::tcgetattr(STDIN_FILENO));
termios no_echo = original;
no_echo.c_lflag &= ~ECHO;
TRY(Core::System::tcsetattr(STDIN_FILENO, TCSAFLUSH, no_echo));
char* password = nullptr;
size_t n = 0;
auto line_length = getline(&password, &n, stdin);
auto saved_errno = errno;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &original);
putchar('\n');
if (line_length < 0)
return Error::from_errno(saved_errno);
VERIFY(line_length != 0);
// Remove trailing '\n' read by getline().
password[line_length - 1] = '\0';
return TRY(SecretString::take_ownership(password, line_length));
}
}

View file

@ -1,16 +0,0 @@
/*
* Copyright (c) 2020, Peter Elliott <pelliott@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <LibCore/SecretString.h>
namespace Core {
ErrorOr<SecretString> get_password(StringView prompt = "Password: "sv);
}

View file

@ -1,187 +0,0 @@
/*
* Copyright (c) 2022, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/CharacterTypes.h>
#include <AK/ScopeGuard.h>
#include <AK/StringBuilder.h>
#include <LibCore/Group.h>
#include <LibCore/System.h>
#include <LibCore/UmaskScope.h>
#include <errno.h>
#include <unistd.h>
namespace Core {
ErrorOr<ByteString> Group::generate_group_file() const
{
StringBuilder builder;
char buffer[1024] = { 0 };
ScopeGuard grent_guard([] { endgrent(); });
setgrent();
while (true) {
auto group = TRY(Core::System::getgrent({ buffer, sizeof(buffer) }));
if (!group.has_value())
break;
if (group->gr_name == m_name)
builder.appendff("{}:x:{}:{}\n", m_name, m_id, ByteString::join(',', m_members));
else {
Vector<ByteString> members;
if (group->gr_mem) {
for (size_t i = 0; group->gr_mem[i]; ++i)
members.append(group->gr_mem[i]);
}
builder.appendff("{}:x:{}:{}\n", group->gr_name, group->gr_gid, ByteString::join(',', members));
}
}
return builder.to_byte_string();
}
ErrorOr<void> Group::sync()
{
Core::UmaskScope umask_scope(0777);
auto new_group_file_content = TRY(generate_group_file());
char new_group_file[] = "/etc/group.XXXXXX";
auto new_group_file_view = StringView { new_group_file, sizeof(new_group_file) };
{
auto new_group_fd = TRY(Core::System::mkstemp(new_group_file));
ScopeGuard new_group_fd_guard([new_group_fd] { close(new_group_fd); });
TRY(Core::System::fchmod(new_group_fd, 0664));
auto nwritten = TRY(Core::System::write(new_group_fd, new_group_file_content.bytes()));
VERIFY(static_cast<size_t>(nwritten) == new_group_file_content.length());
}
TRY(Core::System::rename(new_group_file_view, "/etc/group"sv));
return {};
}
#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_ANDROID) && !defined(AK_OS_HAIKU)
ErrorOr<void> Group::add_group(Group& group)
{
if (group.name().is_empty())
return Error::from_string_literal("Group name can not be empty.");
// A quick sanity check on group name
if (group.name().find_any_of("\\/!@#$%^&*()~+=`:\n"sv, ByteString::SearchDirection::Forward).has_value())
return Error::from_string_literal("Group name has invalid characters.");
// Disallow names starting with '_', '-' or other non-alpha characters.
if (group.name().starts_with('_') || group.name().starts_with('-') || !is_ascii_alpha(group.name().characters()[0]))
return Error::from_string_literal("Group name has invalid characters.");
// Verify group name does not already exist
if (TRY(name_exists(group.name())))
return Error::from_string_literal("Group name already exists.");
// Sort out the group id for the group
if (group.id() > 0) {
if (TRY(id_exists(group.id())))
return Error::from_string_literal("Group ID already exists.");
} else {
gid_t group_id = 100;
while (true) {
if (!TRY(id_exists(group_id)))
break;
group_id++;
}
group.set_group_id(group_id);
}
auto gr = TRY(group.to_libc_group());
FILE* file = fopen("/etc/group", "a");
if (!file)
return Error::from_errno(errno);
ScopeGuard file_guard { [&] {
fclose(file);
} };
if (putgrent(&gr, file) < 0)
return Error::from_errno(errno);
return {};
}
#endif
ErrorOr<Vector<Group>> Group::all()
{
Vector<Group> groups;
char buffer[1024] = { 0 };
ScopeGuard grent_guard([] { endgrent(); });
setgrent();
while (true) {
auto group = TRY(Core::System::getgrent({ buffer, sizeof(buffer) }));
if (!group.has_value())
break;
Vector<ByteString> members;
if (group->gr_mem) {
for (size_t i = 0; group->gr_mem[i]; ++i)
members.append(group->gr_mem[i]);
}
groups.append({ group->gr_name, group->gr_gid, move(members) });
}
return groups;
}
Group::Group(ByteString name, gid_t id, Vector<ByteString> members)
: m_name(move(name))
, m_id(id)
, m_members(move(members))
{
}
ErrorOr<bool> Group::name_exists(StringView name)
{
return TRY(Core::System::getgrnam(name)).has_value();
}
ErrorOr<bool> Group::id_exists(gid_t id)
{
return TRY(Core::System::getgrgid(id)).has_value();
}
// NOTE: struct group returned from this function cannot outlive an instance of Group.
ErrorOr<struct group> Group::to_libc_group()
{
struct group gr;
gr.gr_name = const_cast<char*>(m_name.characters());
gr.gr_passwd = const_cast<char*>("x");
gr.gr_gid = m_id;
gr.gr_mem = nullptr;
// FIXME: A better solution would surely be not using a static here
// NOTE: This now means that there cannot be multiple struct groups at the same time, because only one gr.gr_mem can ever be valid at the same time.
// NOTE: Not using a static here would result in gr.gr_mem being freed up on exit from this function.
static Vector<char*> members;
members.clear_with_capacity();
if (m_members.size() > 0) {
TRY(members.try_ensure_capacity(m_members.size() + 1));
for (auto member : m_members)
members.unchecked_append(const_cast<char*>(member.characters()));
members.unchecked_append(nullptr);
gr.gr_mem = const_cast<char**>(members.data());
}
return gr;
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright (c) 2022, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/Error.h>
#include <AK/Vector.h>
#include <grp.h>
namespace Core {
class Group {
public:
#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_ANDROID) && !defined(AK_OS_HAIKU)
static ErrorOr<void> add_group(Group& group);
#endif
static ErrorOr<Vector<Group>> all();
Group() = default;
Group(ByteString name, gid_t id = 0, Vector<ByteString> members = {});
~Group() = default;
ByteString const& name() const { return m_name; }
void set_name(ByteString const& name) { m_name = name; }
gid_t id() const { return m_id; }
void set_group_id(gid_t const id) { m_id = id; }
Vector<ByteString>& members() { return m_members; }
ErrorOr<void> sync();
private:
static ErrorOr<bool> name_exists(StringView name);
static ErrorOr<bool> id_exists(gid_t id);
ErrorOr<struct group> to_libc_group();
ErrorOr<ByteString> generate_group_file() const;
ByteString m_name;
gid_t m_id { 0 };
Vector<ByteString> m_members;
};
}

View file

@ -1,57 +0,0 @@
/*
* Copyright (c) 2021, Peter Elliott <pelliott@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/Directory.h>
#include <LibCore/LockFile.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/file.h>
#include <unistd.h>
namespace Core {
LockFile::LockFile(char const* filename, Type type)
: m_filename(filename)
{
if (Core::Directory::create(LexicalPath(m_filename).parent(), Core::Directory::CreateDirectories::Yes).is_error())
return;
m_fd = open(filename, O_RDONLY | O_CREAT | O_CLOEXEC, 0666);
if (m_fd == -1) {
m_errno = errno;
return;
}
if (flock(m_fd, LOCK_NB | ((type == Type::Exclusive) ? LOCK_EX : LOCK_SH)) == -1) {
m_errno = errno;
close(m_fd);
m_fd = -1;
}
}
LockFile::~LockFile()
{
release();
}
bool LockFile::is_held() const
{
return m_fd != -1;
}
void LockFile::release()
{
if (m_fd == -1)
return;
unlink(m_filename);
flock(m_fd, LOCK_NB | LOCK_UN);
close(m_fd);
m_fd = -1;
}
}

View file

@ -1,32 +0,0 @@
/*
* Copyright (c) 2021, Peter Elliott <pelliott@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
namespace Core {
class LockFile {
public:
enum class Type {
Exclusive,
Shared
};
LockFile(LockFile const& other) = delete;
LockFile(char const* filename, Type type = Type::Exclusive);
~LockFile();
bool is_held() const;
int error_code() const { return m_errno; }
void release();
private:
int m_fd { -1 };
int m_errno { 0 };
char const* m_filename { nullptr };
};
}

View file

@ -1,112 +0,0 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteBuffer.h>
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <LibCore/File.h>
#include <LibCore/ProcessStatisticsReader.h>
#include <pwd.h>
namespace Core {
HashMap<uid_t, ByteString> ProcessStatisticsReader::s_usernames;
ErrorOr<AllProcessesStatistics> ProcessStatisticsReader::get_all(SeekableStream& proc_all_file, bool include_usernames)
{
TRY(proc_all_file.seek(0, SeekMode::SetPosition));
AllProcessesStatistics all_processes_statistics;
auto file_contents = TRY(proc_all_file.read_until_eof());
auto json_obj = TRY(JsonValue::from_string(file_contents)).as_object();
json_obj.get_array("processes"sv)->for_each([&](auto& value) {
JsonObject const& process_object = value.as_object();
Core::ProcessStatistics process;
// kernel data first
process.pid = process_object.get_u32("pid"sv).value_or(0);
process.pgid = process_object.get_u32("pgid"sv).value_or(0);
process.pgp = process_object.get_u32("pgp"sv).value_or(0);
process.sid = process_object.get_u32("sid"sv).value_or(0);
process.uid = process_object.get_u32("uid"sv).value_or(0);
process.gid = process_object.get_u32("gid"sv).value_or(0);
process.ppid = process_object.get_u32("ppid"sv).value_or(0);
process.kernel = process_object.get_bool("kernel"sv).value_or(false);
process.name = process_object.get_byte_string("name"sv).value_or("");
process.executable = process_object.get_byte_string("executable"sv).value_or("");
process.tty = process_object.get_byte_string("tty"sv).value_or("");
process.pledge = process_object.get_byte_string("pledge"sv).value_or("");
process.veil = process_object.get_byte_string("veil"sv).value_or("");
process.creation_time = UnixDateTime::from_nanoseconds_since_epoch(process_object.get_i64("creation_time"sv).value_or(0));
process.amount_virtual = process_object.get_u32("amount_virtual"sv).value_or(0);
process.amount_resident = process_object.get_u32("amount_resident"sv).value_or(0);
process.amount_shared = process_object.get_u32("amount_shared"sv).value_or(0);
process.amount_dirty_private = process_object.get_u32("amount_dirty_private"sv).value_or(0);
process.amount_clean_inode = process_object.get_u32("amount_clean_inode"sv).value_or(0);
process.amount_purgeable_volatile = process_object.get_u32("amount_purgeable_volatile"sv).value_or(0);
process.amount_purgeable_nonvolatile = process_object.get_u32("amount_purgeable_nonvolatile"sv).value_or(0);
auto& thread_array = process_object.get_array("threads"sv).value();
process.threads.ensure_capacity(thread_array.size());
thread_array.for_each([&](auto& value) {
auto& thread_object = value.as_object();
Core::ThreadStatistics thread;
thread.tid = thread_object.get_u32("tid"sv).value_or(0);
thread.times_scheduled = thread_object.get_u32("times_scheduled"sv).value_or(0);
thread.name = thread_object.get_byte_string("name"sv).value_or("");
thread.state = thread_object.get_byte_string("state"sv).value_or("");
thread.time_user = thread_object.get_u64("time_user"sv).value_or(0);
thread.time_kernel = thread_object.get_u64("time_kernel"sv).value_or(0);
thread.cpu = thread_object.get_u32("cpu"sv).value_or(0);
thread.priority = thread_object.get_u32("priority"sv).value_or(0);
thread.syscall_count = thread_object.get_u32("syscall_count"sv).value_or(0);
thread.inode_faults = thread_object.get_u32("inode_faults"sv).value_or(0);
thread.zero_faults = thread_object.get_u32("zero_faults"sv).value_or(0);
thread.cow_faults = thread_object.get_u32("cow_faults"sv).value_or(0);
thread.unix_socket_read_bytes = thread_object.get_u64("unix_socket_read_bytes"sv).value_or(0);
thread.unix_socket_write_bytes = thread_object.get_u64("unix_socket_write_bytes"sv).value_or(0);
thread.ipv4_socket_read_bytes = thread_object.get_u64("ipv4_socket_read_bytes"sv).value_or(0);
thread.ipv4_socket_write_bytes = thread_object.get_u64("ipv4_socket_write_bytes"sv).value_or(0);
thread.file_read_bytes = thread_object.get_u64("file_read_bytes"sv).value_or(0);
thread.file_write_bytes = thread_object.get_u64("file_write_bytes"sv).value_or(0);
process.threads.append(move(thread));
});
// and synthetic data last
if (include_usernames) {
process.username = username_from_uid(process.uid);
}
all_processes_statistics.processes.append(move(process));
});
all_processes_statistics.total_time_scheduled = json_obj.get_u64("total_time"sv).value_or(0);
all_processes_statistics.total_time_scheduled_kernel = json_obj.get_u64("total_time_kernel"sv).value_or(0);
return all_processes_statistics;
}
ErrorOr<AllProcessesStatistics> ProcessStatisticsReader::get_all(bool include_usernames)
{
auto proc_all_file = TRY(Core::File::open("/sys/kernel/processes"sv, Core::File::OpenMode::Read));
return get_all(*proc_all_file, include_usernames);
}
ByteString ProcessStatisticsReader::username_from_uid(uid_t uid)
{
if (s_usernames.is_empty()) {
setpwent();
while (auto* passwd = getpwent())
s_usernames.set(passwd->pw_uid, passwd->pw_name);
endpwent();
}
auto it = s_usernames.find(uid);
if (it != s_usernames.end())
return (*it).value;
return ByteString::number(uid);
}
}

View file

@ -1,84 +0,0 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/Time.h>
#include <AK/Vector.h>
#include <unistd.h>
namespace Core {
struct ThreadStatistics {
pid_t tid;
unsigned times_scheduled;
u64 time_user;
u64 time_kernel;
unsigned syscall_count;
unsigned inode_faults;
unsigned zero_faults;
unsigned cow_faults;
u64 unix_socket_read_bytes;
u64 unix_socket_write_bytes;
u64 ipv4_socket_read_bytes;
u64 ipv4_socket_write_bytes;
u64 file_read_bytes;
u64 file_write_bytes;
ByteString state;
u32 cpu;
u32 priority;
ByteString name;
};
struct ProcessStatistics {
// Keep this in sync with /sys/kernel/processes.
// From the kernel side:
pid_t pid;
pid_t pgid;
pid_t pgp;
pid_t sid;
uid_t uid;
gid_t gid;
pid_t ppid;
bool kernel;
ByteString name;
ByteString executable;
ByteString tty;
ByteString pledge;
ByteString veil;
UnixDateTime creation_time;
size_t amount_virtual;
size_t amount_resident;
size_t amount_shared;
size_t amount_dirty_private;
size_t amount_clean_inode;
size_t amount_purgeable_volatile;
size_t amount_purgeable_nonvolatile;
Vector<Core::ThreadStatistics> threads;
// synthetic
ByteString username;
};
struct AllProcessesStatistics {
Vector<ProcessStatistics> processes;
u64 total_time_scheduled;
u64 total_time_scheduled_kernel;
};
class ProcessStatisticsReader {
public:
static ErrorOr<AllProcessesStatistics> get_all(SeekableStream&, bool include_usernames = true);
static ErrorOr<AllProcessesStatistics> get_all(bool include_usernames = true);
private:
static ByteString username_from_uid(uid_t);
static HashMap<uid_t, ByteString> s_usernames;
};
}

View file

@ -1,311 +0,0 @@
/*
* Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/MemoryStream.h>
#include <LibCore/SOCKSProxyClient.h>
enum class Method : u8 {
NoAuth = 0x00,
GSSAPI = 0x01,
UsernamePassword = 0x02,
NoAcceptableMethods = 0xFF,
};
enum class AddressType : u8 {
IPV4 = 0x01,
DomainName = 0x03,
IPV6 = 0x04,
};
enum class Reply {
Succeeded = 0x00,
GeneralSocksServerFailure = 0x01,
ConnectionNotAllowedByRuleset = 0x02,
NetworkUnreachable = 0x03,
HostUnreachable = 0x04,
ConnectionRefused = 0x05,
TTLExpired = 0x06,
CommandNotSupported = 0x07,
AddressTypeNotSupported = 0x08,
};
struct [[gnu::packed]] Socks5VersionIdentifierAndMethodSelectionMessage {
u8 version_identifier;
u8 method_count;
// NOTE: We only send a single method, so we don't need to make this variable-length.
u8 methods[1];
};
template<>
struct AK::Traits<Socks5VersionIdentifierAndMethodSelectionMessage> : public AK::DefaultTraits<Socks5VersionIdentifierAndMethodSelectionMessage> {
static constexpr bool is_trivially_serializable() { return true; }
};
struct [[gnu::packed]] Socks5InitialResponse {
u8 version_identifier;
u8 method;
};
template<>
struct AK::Traits<Socks5InitialResponse> : public AK::DefaultTraits<Socks5InitialResponse> {
static constexpr bool is_trivially_serializable() { return true; }
};
struct [[gnu::packed]] Socks5ConnectRequestHeader {
u8 version_identifier;
u8 command;
u8 reserved;
};
template<>
struct AK::Traits<Socks5ConnectRequestHeader> : public AK::DefaultTraits<Socks5ConnectRequestHeader> {
static constexpr bool is_trivially_serializable() { return true; }
};
struct [[gnu::packed]] Socks5ConnectRequestTrailer {
u16 port;
};
template<>
struct AK::Traits<Socks5ConnectRequestTrailer> : public AK::DefaultTraits<Socks5ConnectRequestTrailer> {
static constexpr bool is_trivially_serializable() { return true; }
};
struct [[gnu::packed]] Socks5ConnectResponseHeader {
u8 version_identifier;
u8 status;
u8 reserved;
};
template<>
struct AK::Traits<Socks5ConnectResponseHeader> : public AK::DefaultTraits<Socks5ConnectResponseHeader> {
static constexpr bool is_trivially_serializable() { return true; }
};
struct [[gnu::packed]] Socks5ConnectResponseTrailer {
u8 bind_port;
};
struct [[gnu::packed]] Socks5UsernamePasswordResponse {
u8 version_identifier;
u8 status;
};
template<>
struct AK::Traits<Socks5UsernamePasswordResponse> : public AK::DefaultTraits<Socks5UsernamePasswordResponse> {
static constexpr bool is_trivially_serializable() { return true; }
};
namespace {
StringView reply_response_name(Reply reply)
{
switch (reply) {
case Reply::Succeeded:
return "Succeeded"sv;
case Reply::GeneralSocksServerFailure:
return "GeneralSocksServerFailure"sv;
case Reply::ConnectionNotAllowedByRuleset:
return "ConnectionNotAllowedByRuleset"sv;
case Reply::NetworkUnreachable:
return "NetworkUnreachable"sv;
case Reply::HostUnreachable:
return "HostUnreachable"sv;
case Reply::ConnectionRefused:
return "ConnectionRefused"sv;
case Reply::TTLExpired:
return "TTLExpired"sv;
case Reply::CommandNotSupported:
return "CommandNotSupported"sv;
case Reply::AddressTypeNotSupported:
return "AddressTypeNotSupported"sv;
}
VERIFY_NOT_REACHED();
}
ErrorOr<void> send_version_identifier_and_method_selection_message(Core::Socket& socket, Core::SOCKSProxyClient::Version version, Method method)
{
Socks5VersionIdentifierAndMethodSelectionMessage message {
.version_identifier = to_underlying(version),
.method_count = 1,
.methods = { to_underlying(method) },
};
TRY(socket.write_value(message));
auto response = TRY(socket.read_value<Socks5InitialResponse>());
if (response.version_identifier != to_underlying(version))
return Error::from_string_literal("SOCKS negotiation failed: Invalid version identifier");
if (response.method != to_underlying(method))
return Error::from_string_literal("SOCKS negotiation failed: Failed to negotiate a method");
return {};
}
ErrorOr<Reply> send_connect_request_message(Core::Socket& socket, Core::SOCKSProxyClient::Version version, Core::SOCKSProxyClient::HostOrIPV4 target, int port, Core::SOCKSProxyClient::Command command)
{
AllocatingMemoryStream stream;
Socks5ConnectRequestHeader header {
.version_identifier = to_underlying(version),
.command = to_underlying(command),
.reserved = 0,
};
Socks5ConnectRequestTrailer trailer {
.port = htons(port),
};
TRY(stream.write_value(header));
TRY(target.visit(
[&](ByteString const& hostname) -> ErrorOr<void> {
u8 address_data[2];
address_data[0] = to_underlying(AddressType::DomainName);
address_data[1] = hostname.length();
TRY(stream.write_until_depleted({ address_data, sizeof(address_data) }));
TRY(stream.write_until_depleted({ hostname.characters(), hostname.length() }));
return {};
},
[&](u32 ipv4) -> ErrorOr<void> {
u8 address_data[5];
address_data[0] = to_underlying(AddressType::IPV4);
u32 network_ordered_ipv4 = NetworkOrdered<u32>(ipv4);
memcpy(address_data + 1, &network_ordered_ipv4, sizeof(network_ordered_ipv4));
TRY(stream.write_until_depleted({ address_data, sizeof(address_data) }));
return {};
}));
TRY(stream.write_value(trailer));
auto buffer = TRY(ByteBuffer::create_uninitialized(stream.used_buffer_size()));
TRY(stream.read_until_filled(buffer.bytes()));
TRY(socket.write_until_depleted(buffer));
auto response_header = TRY(socket.read_value<Socks5ConnectResponseHeader>());
if (response_header.version_identifier != to_underlying(version))
return Error::from_string_literal("SOCKS negotiation failed: Invalid version identifier");
auto response_address_type = TRY(socket.read_value<u8>());
switch (AddressType(response_address_type)) {
case AddressType::IPV4: {
u8 response_address_data[4];
TRY(socket.read_until_filled({ response_address_data, sizeof(response_address_data) }));
break;
}
case AddressType::DomainName: {
auto response_address_length = TRY(socket.read_value<u8>());
auto buffer = TRY(ByteBuffer::create_uninitialized(response_address_length));
TRY(socket.read_until_filled(buffer));
break;
}
case AddressType::IPV6:
default:
return Error::from_string_literal("SOCKS negotiation failed: Invalid connect response address type");
}
[[maybe_unused]] auto bound_port = TRY(socket.read_value<u16>());
return Reply(response_header.status);
}
ErrorOr<u8> send_username_password_authentication_message(Core::Socket& socket, Core::SOCKSProxyClient::UsernamePasswordAuthenticationData const& auth_data)
{
AllocatingMemoryStream stream;
u8 version = 0x01;
TRY(stream.write_value(version));
u8 username_length = auth_data.username.length();
TRY(stream.write_value(username_length));
TRY(stream.write_until_depleted({ auth_data.username.characters(), auth_data.username.length() }));
u8 password_length = auth_data.password.length();
TRY(stream.write_value(password_length));
TRY(stream.write_until_depleted({ auth_data.password.characters(), auth_data.password.length() }));
auto buffer = TRY(ByteBuffer::create_uninitialized(stream.used_buffer_size()));
TRY(stream.read_until_filled(buffer.bytes()));
TRY(socket.write_until_depleted(buffer));
auto response = TRY(socket.read_value<Socks5UsernamePasswordResponse>());
if (response.version_identifier != version)
return Error::from_string_literal("SOCKS negotiation failed: Invalid version identifier");
return response.status;
}
}
namespace Core {
SOCKSProxyClient::~SOCKSProxyClient()
{
close();
m_socket.on_ready_to_read = nullptr;
}
ErrorOr<NonnullOwnPtr<SOCKSProxyClient>> SOCKSProxyClient::connect(Socket& underlying, Version version, HostOrIPV4 const& target, int target_port, Variant<UsernamePasswordAuthenticationData, Empty> const& auth_data, Command command)
{
if (version != Version::V5)
return Error::from_string_literal("SOCKS version not supported");
return auth_data.visit(
[&](Empty) -> ErrorOr<NonnullOwnPtr<SOCKSProxyClient>> {
TRY(send_version_identifier_and_method_selection_message(underlying, version, Method::NoAuth));
auto reply = TRY(send_connect_request_message(underlying, version, target, target_port, command));
if (reply != Reply::Succeeded) {
underlying.close();
return Error::from_string_view(reply_response_name(reply));
}
return adopt_nonnull_own_or_enomem(new SOCKSProxyClient {
underlying,
nullptr,
});
},
[&](UsernamePasswordAuthenticationData const& auth_data) -> ErrorOr<NonnullOwnPtr<SOCKSProxyClient>> {
TRY(send_version_identifier_and_method_selection_message(underlying, version, Method::UsernamePassword));
auto auth_response = TRY(send_username_password_authentication_message(underlying, auth_data));
if (auth_response != 0) {
underlying.close();
return Error::from_string_literal("SOCKS authentication failed");
}
auto reply = TRY(send_connect_request_message(underlying, version, target, target_port, command));
if (reply != Reply::Succeeded) {
underlying.close();
return Error::from_string_view(reply_response_name(reply));
}
return adopt_nonnull_own_or_enomem(new SOCKSProxyClient {
underlying,
nullptr,
});
});
}
ErrorOr<NonnullOwnPtr<SOCKSProxyClient>> SOCKSProxyClient::connect(HostOrIPV4 const& server, int server_port, Version version, HostOrIPV4 const& target, int target_port, Variant<UsernamePasswordAuthenticationData, Empty> const& auth_data, Command command)
{
auto underlying = TRY(server.visit(
[&](u32 ipv4) {
return Core::TCPSocket::connect({ IPv4Address(ipv4), static_cast<u16>(server_port) });
},
[&](ByteString const& hostname) {
return Core::TCPSocket::connect(hostname, static_cast<u16>(server_port));
}));
auto socket = TRY(connect(*underlying, version, target, target_port, auth_data, command));
socket->m_own_underlying_socket = move(underlying);
dbgln("SOCKS proxy connected, have {} available bytes", TRY(socket->m_socket.pending_bytes()));
return socket;
}
}

View file

@ -1,64 +0,0 @@
/*
* Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/OwnPtr.h>
#include <LibCore/Proxy.h>
#include <LibCore/Socket.h>
namespace Core {
class SOCKSProxyClient final : public Socket {
public:
enum class Version : u8 {
V4 = 0x04,
V5 = 0x05,
};
struct UsernamePasswordAuthenticationData {
ByteString username;
ByteString password;
};
enum class Command : u8 {
Connect = 0x01,
Bind = 0x02,
UDPAssociate = 0x03,
};
using HostOrIPV4 = Variant<ByteString, u32>;
static ErrorOr<NonnullOwnPtr<SOCKSProxyClient>> connect(Socket& underlying, Version, HostOrIPV4 const& target, int target_port, Variant<UsernamePasswordAuthenticationData, Empty> const& auth_data = {}, Command = Command::Connect);
static ErrorOr<NonnullOwnPtr<SOCKSProxyClient>> connect(HostOrIPV4 const& server, int server_port, Version, HostOrIPV4 const& target, int target_port, Variant<UsernamePasswordAuthenticationData, Empty> const& auth_data = {}, Command = Command::Connect);
virtual ~SOCKSProxyClient() override;
// ^Stream::Stream
virtual ErrorOr<Bytes> read_some(Bytes bytes) override { return m_socket.read_some(bytes); }
virtual ErrorOr<size_t> write_some(ReadonlyBytes bytes) override { return m_socket.write_some(bytes); }
virtual bool is_eof() const override { return m_socket.is_eof(); }
virtual bool is_open() const override { return m_socket.is_open(); }
virtual void close() override { m_socket.close(); }
// ^Stream::Socket
virtual ErrorOr<size_t> pending_bytes() const override { return m_socket.pending_bytes(); }
virtual ErrorOr<bool> can_read_without_blocking(int timeout = 0) const override { return m_socket.can_read_without_blocking(timeout); }
virtual ErrorOr<void> set_blocking(bool enabled) override { return m_socket.set_blocking(enabled); }
virtual ErrorOr<void> set_close_on_exec(bool enabled) override { return m_socket.set_close_on_exec(enabled); }
virtual void set_notifications_enabled(bool enabled) override { m_socket.set_notifications_enabled(enabled); }
private:
SOCKSProxyClient(Socket& socket, OwnPtr<Socket> own_socket)
: m_socket(socket)
, m_own_underlying_socket(move(own_socket))
{
m_socket.on_ready_to_read = [this] { on_ready_to_read(); };
}
Socket& m_socket;
OwnPtr<Socket> m_own_underlying_socket;
};
}

View file

@ -1,50 +0,0 @@
/*
* Copyright (c) 2021, Brian Gianforcaro <bgianf@serenityos.org>
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Memory.h>
#include <LibCore/SecretString.h>
namespace Core {
ErrorOr<SecretString> SecretString::take_ownership(char*& cstring, size_t length)
{
auto buffer = TRY(ByteBuffer::copy(cstring, length));
secure_zero(cstring, length);
free(cstring);
cstring = nullptr;
return SecretString(move(buffer));
}
SecretString SecretString::take_ownership(ByteBuffer&& buffer)
{
return SecretString(move(buffer));
}
SecretString::SecretString(ByteBuffer&& buffer)
: m_secure_buffer(move(buffer))
{
// SecretString is currently only used to provide the character data to invocations to crypt(),
// which requires a NUL-terminated string. To ensure this operation avoids a buffer overrun,
// append a NUL terminator here if there isn't already one.
if (m_secure_buffer.is_empty() || (m_secure_buffer[m_secure_buffer.size() - 1] != 0)) {
u8 nul = '\0';
m_secure_buffer.append(&nul, 1);
}
}
SecretString::~SecretString()
{
// Note: We use secure_zero to avoid the zeroing from being optimized out by the compiler,
// which is possible if memset was to be used here.
if (!m_secure_buffer.is_empty()) {
secure_zero(m_secure_buffer.data(), m_secure_buffer.capacity());
}
}
}

View file

@ -1,37 +0,0 @@
/*
* Copyright (c) 2021, Brian Gianforcaro <bgianf@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/Noncopyable.h>
#include <AK/StringView.h>
namespace Core {
class SecretString {
AK_MAKE_NONCOPYABLE(SecretString);
AK_MAKE_DEFAULT_MOVABLE(SecretString);
public:
[[nodiscard]] static ErrorOr<SecretString> take_ownership(char*&, size_t);
[[nodiscard]] static SecretString take_ownership(ByteBuffer&&);
[[nodiscard]] bool is_empty() const { return m_secure_buffer.is_empty(); }
[[nodiscard]] size_t length() const { return m_secure_buffer.size(); }
[[nodiscard]] char const* characters() const { return reinterpret_cast<char const*>(m_secure_buffer.data()); }
[[nodiscard]] StringView view() const { return { characters(), length() }; }
SecretString() = default;
~SecretString();
private:
explicit SecretString(ByteBuffer&&);
ByteBuffer m_secure_buffer;
};
}

View file

@ -1,30 +0,0 @@
/*
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <sys/stat.h>
#include <sys/types.h>
namespace Core {
class UmaskScope {
public:
explicit UmaskScope(mode_t mask)
{
m_old_mask = umask(mask);
}
~UmaskScope()
{
umask(m_old_mask);
}
private:
mode_t m_old_mask {};
};
}