diff --git a/Base/etc/SystemServer.ini b/Base/etc/SystemServer.ini index b1f871a228a..ab1874e2d09 100644 --- a/Base/etc/SystemServer.ini +++ b/Base/etc/SystemServer.ini @@ -8,6 +8,16 @@ BootModes=text,graphical,self-test MultiInstance=1 AcceptSocketConnections=1 +[FileSystemAccessServer] +Socket=/tmp/portal/filesystemaccess +SocketPermissions=660 +Lazy=1 +Priority=low +User=anon +BootModes=text,graphical,self-test +MultiInstance=1 +AcceptSocketConnections=1 + [WebContent] Socket=/tmp/portal/webcontent SocketPermissions=600 diff --git a/Userland/Services/CMakeLists.txt b/Userland/Services/CMakeLists.txt index 93e3b606102..c7bfc5ab3a5 100644 --- a/Userland/Services/CMakeLists.txt +++ b/Userland/Services/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(Clipboard) add_subdirectory(CrashDaemon) add_subdirectory(DHCPClient) add_subdirectory(EchoServer) +add_subdirectory(FileSystemAccessServer) add_subdirectory(FileOperation) add_subdirectory(ImageDecoder) add_subdirectory(InspectorServer) diff --git a/Userland/Services/FileSystemAccessServer/CMakeLists.txt b/Userland/Services/FileSystemAccessServer/CMakeLists.txt new file mode 100644 index 00000000000..67eeacb84a3 --- /dev/null +++ b/Userland/Services/FileSystemAccessServer/CMakeLists.txt @@ -0,0 +1,18 @@ +serenity_component( + FileSystemAccessServer + REQUIRED + TARGETS FileSystemAccessServer +) + +compile_ipc(FileSystemAccessServer.ipc FileSystemAccessServerEndpoint.h) +compile_ipc(FileSystemAccessClient.ipc FileSystemAccessClientEndpoint.h) + +set(SOURCES + ClientConnection.cpp + main.cpp + FileSystemAccessServerEndpoint.h + FileSystemAccessClientEndpoint.h +) + +serenity_bin(FileSystemAccessServer) +target_link_libraries(FileSystemAccessServer LibCore LibIPC LibGUI) diff --git a/Userland/Services/FileSystemAccessServer/ClientConnection.cpp b/Userland/Services/FileSystemAccessServer/ClientConnection.cpp new file mode 100644 index 00000000000..3c5979f22b7 --- /dev/null +++ b/Userland/Services/FileSystemAccessServer/ClientConnection.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2021, timmot + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace FileSystemAccessServer { + +static HashMap> s_connections; + +ClientConnection::ClientConnection(NonnullRefPtr socket, int client_id) + : IPC::ClientConnection(*this, move(socket), client_id) +{ + s_connections.set(client_id, *this); +} + +ClientConnection::~ClientConnection() +{ +} + +void ClientConnection::die() +{ + s_connections.remove(client_id()); + GUI::Application::the()->quit(); + exit(0); +} + +Messages::FileSystemAccessServer::RequestFileResponse ClientConnection::request_file(String const& path, Core::OpenMode const& requested_access) +{ + VERIFY(path.starts_with("/"sv)); + + bool approved = false; + auto maybe_permissions = m_approved_files.get(path); + + auto relevant_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly); + VERIFY(relevant_permissions != Core::OpenMode::NotOpen); + + if (maybe_permissions.has_value()) + approved = has_flag(maybe_permissions.value(), relevant_permissions); + + if (!approved) { + StringBuilder builder; + if (has_flag(requested_access, Core::OpenMode::ReadOnly)) + builder.append('r'); + + if (has_flag(requested_access, Core::OpenMode::WriteOnly)) + builder.append('w'); + + auto access_string = builder.to_string(); + + auto pid = this->socket().peer_pid(); + auto exe_link = LexicalPath("/proc").append(String::number(pid)).append("exe").string(); + auto exe_path = Core::File::real_path_for(exe_link); + auto exe_name = LexicalPath::basename(exe_path); + + auto result = GUI::MessageBox::show(nullptr, String::formatted("Give {} ({}) \"{}\" access to \"{}\"?", exe_name, pid, access_string, path), "File Permissions Requested", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::YesNo); + + approved = result == GUI::MessageBox::ExecYes; + + if (approved) { + auto new_permissions = relevant_permissions; + + if (maybe_permissions.has_value()) + new_permissions |= maybe_permissions.value(); + + m_approved_files.set(path, new_permissions); + } + } + + if (approved) { + auto file = Core::File::open(path, requested_access); + + if (file.is_error()) { + dbgln("FileSystemAccessServer: Couldn't open {}, error {}", path, file.error()); + + return { errno, Optional {} }; + } + + return { 0, IPC::File(file.value()->leak_fd(), IPC::File::CloseAfterSending) }; + } + + return { -1, Optional {} }; +} + +Messages::FileSystemAccessServer::PromptOpenFileResponse ClientConnection::prompt_open_file(String const& path_to_view, Core::OpenMode const& requested_access) +{ + auto relevant_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly); + VERIFY(relevant_permissions != Core::OpenMode::NotOpen); + + auto main_window = GUI::Window::construct(); + auto user_picked_file = GUI::FilePicker::get_open_filepath(main_window, "Select file", path_to_view); + + return prompt_helper(user_picked_file, requested_access); +} + +Messages::FileSystemAccessServer::PromptSaveFileResponse ClientConnection::prompt_save_file(String const& name, String const& ext, String const& path_to_view, Core::OpenMode const& requested_access) +{ + auto relevant_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly); + VERIFY(relevant_permissions != Core::OpenMode::NotOpen); + + auto main_window = GUI::Window::construct(); + auto user_picked_file = GUI::FilePicker::get_save_filepath(main_window, name, ext, path_to_view); + + return prompt_helper(user_picked_file, requested_access); +} + +template +T ClientConnection::prompt_helper(Optional const& user_picked_file, Core::OpenMode const& requested_access) +{ + if (user_picked_file.has_value()) { + VERIFY(user_picked_file->starts_with("/"sv)); + auto file = Core::File::open(user_picked_file.value(), requested_access); + + if (file.is_error()) { + dbgln("FileSystemAccessServer: Couldn't open {}, error {}", user_picked_file.value(), file.error()); + + return { errno, Optional {}, Optional {} }; + } + + auto maybe_permissions = m_approved_files.get(user_picked_file.value()); + auto new_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly); + if (maybe_permissions.has_value()) + new_permissions |= maybe_permissions.value(); + + m_approved_files.set(user_picked_file.value(), new_permissions); + + return { 0, IPC::File(file.value()->leak_fd(), IPC::File::CloseAfterSending), user_picked_file.value() }; + } + + return { -1, Optional {}, Optional {} }; +} + +} diff --git a/Userland/Services/FileSystemAccessServer/ClientConnection.h b/Userland/Services/FileSystemAccessServer/ClientConnection.h new file mode 100644 index 00000000000..2f738bb4e72 --- /dev/null +++ b/Userland/Services/FileSystemAccessServer/ClientConnection.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021, timmot + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace FileSystemAccessServer { + +class ClientConnection final + : public IPC::ClientConnection { + C_OBJECT(ClientConnection); + +public: + explicit ClientConnection(NonnullRefPtr, int client_id); + ~ClientConnection() override; + + virtual void die() override; + +private: + virtual Messages::FileSystemAccessServer::RequestFileResponse request_file(String const&, Core::OpenMode const&) override; + virtual Messages::FileSystemAccessServer::PromptOpenFileResponse prompt_open_file(String const&, Core::OpenMode const&) override; + virtual Messages::FileSystemAccessServer::PromptSaveFileResponse prompt_save_file(String const&, String const&, String const&, Core::OpenMode const&) override; + + template + T prompt_helper(Optional const&, Core::OpenMode const&); + + HashMap m_approved_files; +}; + +} diff --git a/Userland/Services/FileSystemAccessServer/FileSystemAccessClient.ipc b/Userland/Services/FileSystemAccessServer/FileSystemAccessClient.ipc new file mode 100644 index 00000000000..fa99132c8f8 --- /dev/null +++ b/Userland/Services/FileSystemAccessServer/FileSystemAccessClient.ipc @@ -0,0 +1,3 @@ +endpoint FileSystemAccessClient +{ +} diff --git a/Userland/Services/FileSystemAccessServer/FileSystemAccessServer.ipc b/Userland/Services/FileSystemAccessServer/FileSystemAccessServer.ipc new file mode 100644 index 00000000000..6f10d9ba618 --- /dev/null +++ b/Userland/Services/FileSystemAccessServer/FileSystemAccessServer.ipc @@ -0,0 +1,9 @@ +#include +#include + +endpoint FileSystemAccessServer +{ + request_file(String path, Core::OpenMode requested_access) => (i32 error, Optional fd) + prompt_open_file(String path_to_view, Core::OpenMode requested_access) => (i32 error, Optional fd, Optional chosen_file) + prompt_save_file(String title, String ext, String path_to_view, Core::OpenMode requested_access) => (i32 error, Optional fd, Optional chosen_file) +} diff --git a/Userland/Services/FileSystemAccessServer/main.cpp b/Userland/Services/FileSystemAccessServer/main.cpp new file mode 100644 index 00000000000..484ac595e4b --- /dev/null +++ b/Userland/Services/FileSystemAccessServer/main.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021, timmot + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +int main(int, char**) +{ + if (pledge("stdio recvfd sendfd rpath cpath wpath unix thread", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto app = GUI::Application::construct(0, nullptr); + app->set_quit_when_last_window_deleted(false); + + auto socket = Core::LocalSocket::take_over_accepted_socket_from_system_server(); + IPC::new_client_connection(socket.release_nonnull(), 1); + return app->exec(); +}