summaryrefslogtreecommitdiff
path: root/Userland/Applications/Escalator
diff options
context:
space:
mode:
authorne0ndrag0n <ne0ndrag0n@users.noreply.github.com>2022-10-01 00:40:28 -0400
committerLinus Groh <mail@linusgroh.de>2022-10-14 11:23:06 +0200
commitb4456ecdbbe6aa9e0187c06458acc51e50ced512 (patch)
tree59ffb4fe862d246cb72879863e1f7b19b96c2602 /Userland/Applications/Escalator
parenta143d666db14369f26da3d8590443083b382d8fe (diff)
downloadserenity-b4456ecdbbe6aa9e0187c06458acc51e50ced512.zip
Escalator: Add new method to privilege escalate within GUI
Diffstat (limited to 'Userland/Applications/Escalator')
-rw-r--r--Userland/Applications/Escalator/CMakeLists.txt19
-rw-r--r--Userland/Applications/Escalator/Escalator.gml49
-rw-r--r--Userland/Applications/Escalator/EscalatorWindow.cpp114
-rw-r--r--Userland/Applications/Escalator/EscalatorWindow.h47
-rw-r--r--Userland/Applications/Escalator/main.cpp53
5 files changed, 282 insertions, 0 deletions
diff --git a/Userland/Applications/Escalator/CMakeLists.txt b/Userland/Applications/Escalator/CMakeLists.txt
new file mode 100644
index 0000000000..e8a1749311
--- /dev/null
+++ b/Userland/Applications/Escalator/CMakeLists.txt
@@ -0,0 +1,19 @@
+serenity_component(
+ Escalator
+ REQUIRED
+ TARGETS Escalator
+)
+
+compile_gml(Escalator.gml EscalatorGML.h escalator_gml)
+
+set(SOURCES
+ main.cpp
+ EscalatorWindow.cpp
+)
+
+set(GENERATED_SOURCES
+ EscalatorGML.h
+)
+
+serenity_app(Escalator ICON app-escalator)
+target_link_libraries(Escalator LibCore LibDesktop LibGUI LibMain)
diff --git a/Userland/Applications/Escalator/Escalator.gml b/Userland/Applications/Escalator/Escalator.gml
new file mode 100644
index 0000000000..08ec1fb3ed
--- /dev/null
+++ b/Userland/Applications/Escalator/Escalator.gml
@@ -0,0 +1,49 @@
+@GUI::Widget {
+ fill_with_background_color: true
+ layout: @GUI::VerticalBoxLayout {
+ margins: [4]
+ }
+
+ @GUI::Widget {
+ max_height: 32
+ layout: @GUI::HorizontalBoxLayout {
+ spacing: 16
+ }
+
+ @GUI::ImageWidget {
+ name: "icon"
+ }
+
+ @GUI::Label {
+ name: "description"
+ text_alignment: "CenterLeft"
+ }
+ }
+
+ @GUI::Widget {
+ layout: @GUI::HorizontalBoxLayout {
+ margins: [4]
+ }
+
+ @GUI::PasswordBox {
+ name: "password"
+ }
+ }
+
+ @GUI::Widget {
+ layout: @GUI::HorizontalBoxLayout {}
+ fixed_height: 22
+
+ @GUI::Layout::Spacer {}
+
+ @GUI::DialogButton {
+ name: "ok_button"
+ text: "OK"
+ }
+
+ @GUI::DialogButton {
+ name: "cancel_button"
+ text: "Cancel"
+ }
+ }
+}
diff --git a/Userland/Applications/Escalator/EscalatorWindow.cpp b/Userland/Applications/Escalator/EscalatorWindow.cpp
new file mode 100644
index 0000000000..9b2927fe63
--- /dev/null
+++ b/Userland/Applications/Escalator/EscalatorWindow.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2022, Ashley N. <dev-serenity@ne0ndrag0n.com>
+ * Copyright (c) 2022, the SerenityOS developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "EscalatorWindow.h"
+#include <AK/Assertions.h>
+#include <Applications/Escalator/EscalatorGML.h>
+#include <LibCore/SecretString.h>
+#include <LibCore/System.h>
+#include <LibGUI/FileIconProvider.h>
+#include <LibGUI/Icon.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGUI/Widget.h>
+#include <unistd.h>
+
+EscalatorWindow::EscalatorWindow(StringView executable, Vector<StringView> arguments, EscalatorWindow::Options const& options)
+ : m_arguments(arguments)
+ , m_executable(executable)
+ , m_current_user(options.current_user)
+ , m_preserve_env(options.preserve_env)
+{
+ auto app_icon = GUI::FileIconProvider::icon_for_executable(m_executable);
+
+ set_title("Run as Root");
+ set_icon(app_icon.bitmap_for_size(16));
+ resize(345, 100);
+ set_resizable(false);
+ set_minimizable(false);
+
+ auto& main_widget = set_main_widget<GUI::Widget>();
+ main_widget.load_from_gml(escalator_gml);
+
+ RefPtr<GUI::Label> app_label = *main_widget.find_descendant_of_type_named<GUI::Label>("description");
+
+ String prompt;
+ if (options.description.is_empty())
+ prompt = String::formatted("{} requires root access. Please enter password for user \"{}\".", m_arguments[0], m_current_user.username());
+ else
+ prompt = options.description;
+
+ app_label->set_text(prompt);
+
+ m_icon_image_widget = *main_widget.find_descendant_of_type_named<GUI::ImageWidget>("icon");
+ m_icon_image_widget->set_bitmap(app_icon.bitmap_for_size(32));
+
+ m_ok_button = *main_widget.find_descendant_of_type_named<GUI::DialogButton>("ok_button");
+ m_ok_button->on_click = [this](auto) {
+ auto result = check_password();
+ if (result.is_error()) {
+ GUI::MessageBox::show_error(this, String::formatted("Failed to execute command: {}", result.error()));
+ close();
+ }
+ };
+ m_ok_button->set_default(true);
+
+ m_cancel_button = *main_widget.find_descendant_of_type_named<GUI::DialogButton>("cancel_button");
+ m_cancel_button->on_click = [this](auto) {
+ close();
+ };
+
+ m_password_input = *main_widget.find_descendant_of_type_named<GUI::PasswordBox>("password");
+}
+
+ErrorOr<void> EscalatorWindow::check_password()
+{
+ String password = m_password_input->text();
+ if (password.is_empty()) {
+ GUI::MessageBox::show_error(this, "Please enter a password."sv);
+ return {};
+ }
+
+ // FIXME: PasswordBox really should store its input directly as a SecretString.
+ Core::SecretString password_secret = Core::SecretString::take_ownership(password.to_byte_buffer());
+ if (!m_current_user.authenticate(password_secret)) {
+ GUI::MessageBox::show_error(this, "Incorrect or disabled password."sv);
+ m_password_input->select_all();
+ return {};
+ }
+
+ // Caller will close Escalator if error is returned.
+ TRY(execute_command());
+ VERIFY_NOT_REACHED();
+}
+
+ErrorOr<void> EscalatorWindow::execute_command()
+{
+ // Translate environ to format for Core::System::exec.
+ Vector<StringView> exec_environment;
+ for (size_t i = 0; environ[i]; ++i) {
+ StringView env_view { environ[i], strlen(environ[i]) };
+ auto maybe_needle = env_view.find('=');
+
+ if (!maybe_needle.has_value())
+ continue;
+
+ if (!m_preserve_env && env_view.substring_view(0, maybe_needle.value()) != "TERM"sv)
+ continue;
+
+ exec_environment.append(env_view);
+ }
+
+ // Escalate process privilege to root user.
+ TRY(Core::System::seteuid(0));
+ auto root_user = TRY(Core::Account::from_uid(0));
+ TRY(root_user.login());
+
+ TRY(Core::System::pledge("stdio sendfd rpath exec"));
+ TRY(Core::System::exec(m_executable, m_arguments, Core::System::SearchInPath::No, exec_environment));
+ VERIFY_NOT_REACHED();
+}
diff --git a/Userland/Applications/Escalator/EscalatorWindow.h b/Userland/Applications/Escalator/EscalatorWindow.h
new file mode 100644
index 0000000000..21c51e7cc5
--- /dev/null
+++ b/Userland/Applications/Escalator/EscalatorWindow.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022, Ashley N. <dev-serenity@ne0ndrag0n.com>
+ * Copyright (c) 2022, the SerenityOS developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/StringView.h>
+#include <AK/Vector.h>
+#include <LibCore/Account.h>
+#include <LibCore/Object.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/ImageWidget.h>
+#include <LibGUI/TextBox.h>
+#include <LibGUI/Window.h>
+
+class EscalatorWindow final : public GUI::Window {
+ C_OBJECT(EscalatorWindow)
+public:
+ struct Options {
+ StringView description;
+ Core::Account current_user;
+ bool preserve_env { false };
+ };
+
+ virtual ~EscalatorWindow() override = default;
+
+ ErrorOr<void> execute_command();
+
+private:
+ EscalatorWindow(StringView executable, Vector<StringView> arguments, Options const& options);
+
+ ErrorOr<void> check_password();
+
+ Vector<StringView> m_arguments;
+ StringView m_executable;
+ Core::Account m_current_user;
+ bool m_preserve_env { false };
+
+ RefPtr<GUI::ImageWidget> m_icon_image_widget;
+ RefPtr<GUI::Button> m_ok_button;
+ RefPtr<GUI::Button> m_cancel_button;
+ RefPtr<GUI::PasswordBox> m_password_input;
+};
diff --git a/Userland/Applications/Escalator/main.cpp b/Userland/Applications/Escalator/main.cpp
new file mode 100644
index 0000000000..0570487e67
--- /dev/null
+++ b/Userland/Applications/Escalator/main.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022, Ashley N. <dev-serenity@ne0ndrag0n.com>
+ * Copyright (c) 2022, the SerenityOS developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "EscalatorWindow.h"
+#include <AK/String.h>
+#include <LibCore/Account.h>
+#include <LibCore/ArgsParser.h>
+#include <LibCore/File.h>
+#include <LibCore/System.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Desktop.h>
+#include <LibGUI/MessageBox.h>
+#include <LibMain/Main.h>
+
+ErrorOr<int> serenity_main(Main::Arguments arguments)
+{
+ Vector<StringView> command;
+ Core::ArgsParser args_parser;
+ StringView description;
+ bool preserve_env = false;
+ args_parser.set_general_help("Escalate privilege to root for a given command using a GUI prompt.");
+ args_parser.set_stop_on_first_non_option(true);
+ args_parser.add_option(description, "Custom prompt to use for dialog", "prompt", 'P', "prompt");
+ args_parser.add_option(preserve_env, "Preserve user environment when running command", "preserve-env", 'E');
+ args_parser.add_positional_argument(command, "Command to run at elevated privilege level", "command");
+ args_parser.parse(arguments);
+
+ TRY(Core::System::pledge("stdio recvfd sendfd thread cpath rpath wpath unix proc exec id"));
+
+ auto app = TRY(GUI::Application::try_create(arguments));
+
+ auto executable_path = Core::File::resolve_executable_from_environment(command[0]);
+ if (!executable_path.has_value()) {
+ GUI::MessageBox::show_error(nullptr, String::formatted("Could not execute command {}: Command not found.", command[0]));
+ return 127;
+ }
+
+ auto current_user = TRY(Core::Account::self());
+ auto window = TRY(EscalatorWindow::try_create(executable_path.value(), command, EscalatorWindow::Options { description, current_user, preserve_env }));
+
+ if (current_user.uid() != 0) {
+ window->show();
+ return app->exec();
+ } else {
+ // Run directly as root if already root uid.
+ TRY(window->execute_command());
+ return 0;
+ }
+}