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
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", "ConfigFile.h",
"DateTime.cpp", "DateTime.cpp",
"DateTime.h", "DateTime.h",
"Debounce.h",
"DeferredInvocationContext.h", "DeferredInvocationContext.h",
"ElapsedTimer.cpp", "ElapsedTimer.cpp",
"ElapsedTimer.h", "ElapsedTimer.h",
@ -58,8 +57,6 @@ source_set("sources") {
"EventLoopImplementationUnix.h", "EventLoopImplementationUnix.h",
"EventReceiver.cpp", "EventReceiver.cpp",
"EventReceiver.h", "EventReceiver.h",
"LockFile.cpp",
"LockFile.h",
"MappedFile.cpp", "MappedFile.cpp",
"MappedFile.h", "MappedFile.h",
"MimeData.cpp", "MimeData.cpp",
@ -71,18 +68,12 @@ source_set("sources") {
"Platform/ProcessStatistics.h", "Platform/ProcessStatistics.h",
"Process.cpp", "Process.cpp",
"Process.h", "Process.h",
"ProcessStatisticsReader.cpp",
"ProcessStatisticsReader.h",
"Promise.h", "Promise.h",
"Proxy.h", "Proxy.h",
"Resource.cpp", "Resource.cpp",
"Resource.h", "Resource.h",
"ResourceImplementation.cpp", "ResourceImplementation.cpp",
"ResourceImplementationFile.cpp", "ResourceImplementationFile.cpp",
"SOCKSProxyClient.cpp",
"SOCKSProxyClient.h",
"SecretString.cpp",
"SecretString.h",
"SessionManagement.cpp", "SessionManagement.cpp",
"SessionManagement.h", "SessionManagement.h",
"SharedCircularQueue.h", "SharedCircularQueue.h",
@ -100,18 +91,9 @@ source_set("sources") {
"Timer.h", "Timer.h",
"UDPServer.cpp", "UDPServer.cpp",
"UDPServer.h", "UDPServer.h",
"UmaskScope.h",
] ]
if (current_os != "android") { if (current_os != "android") {
sources += [ sources += [
"Account.cpp",
"Account.h",
"FilePermissionsMask.cpp",
"FilePermissionsMask.h",
"GetPassword.cpp",
"GetPassword.h",
"Group.cpp",
"Group.h",
"LocalServer.cpp", "LocalServer.cpp",
"LocalServer.h", "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 EventLoopImplementation.cpp
EventLoopImplementationUnix.cpp EventLoopImplementationUnix.cpp
EventReceiver.cpp EventReceiver.cpp
LockFile.cpp
MappedFile.cpp MappedFile.cpp
MimeData.cpp MimeData.cpp
Notifier.cpp Notifier.cpp
Process.cpp Process.cpp
ProcessStatisticsReader.cpp
Resource.cpp Resource.cpp
ResourceImplementation.cpp ResourceImplementation.cpp
ResourceImplementationFile.cpp ResourceImplementationFile.cpp
SecretString.cpp
SessionManagement.cpp SessionManagement.cpp
Socket.cpp Socket.cpp
SOCKSProxyClient.cpp
SystemServerTakeover.cpp SystemServerTakeover.cpp
TCPServer.cpp TCPServer.cpp
ThreadEventQueue.cpp ThreadEventQueue.cpp
Timer.cpp Timer.cpp
UDPServer.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) if (NOT WIN32 AND NOT EMSCRIPTEN)
list(APPEND SOURCES LocalServer.cpp) list(APPEND SOURCES LocalServer.cpp)
endif() 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 {};
};
}