diff --git a/cmake/linux/linux.cmake b/cmake/linux/linux.cmake index b329d6f..f9458e0 100644 --- a/cmake/linux/linux.cmake +++ b/cmake/linux/linux.cmake @@ -1 +1,2 @@ +set(PLATFORM_SOURCES sys/linux/LinuxCap.cpp) set(PLATFORM_LIBRARIES dl) diff --git a/libs/package_debian.sh b/libs/package_debian.sh index 7728913..ff44ff3 100644 --- a/libs/package_debian.sh +++ b/libs/package_debian.sh @@ -19,31 +19,13 @@ Depends: libxcb-xinerama0, libqt5core5a, libqt5gui5, libqt5network5, libqt5widge Description: Qt based cross-platform GUI proxy configuration manager (backend: v2ray / sing-box) EOF -# Start Tun Mode without password -cat >nekoray/opt/nekoray/pkexec <<-EOF -#!/bin/sh - -if [ \$1 = --help ]; then - echo "This is not real pkexec." - exit 0 -fi - -TO_EXEC="\$@" - -if [ \$1 = --keep-cwd ]; then - TO_EXEC="\${@:2}" -fi - -\$TO_EXEC -EOF - cat >nekoray/DEBIAN/postinst <<-EOF if [ ! -s /usr/share/applications/nekoray.desktop ]; then cat >/usr/share/applications/nekoray.desktop<<-END [Desktop Entry] Name=nekoray Comment=Qt based cross-platform GUI proxy configuration manager (backend: Xray / sing-box) -Exec=sh -c "PATH=/opt/nekoray:\$PATH /opt/nekoray/nekoray -flag_linux_run_core_as_admin -appdata" +Exec=sh -c "PATH=/opt/nekoray:\$PATH /opt/nekoray/nekoray -appdata" Icon=/opt/nekoray/nekoray.png Terminal=false Type=Application @@ -52,8 +34,6 @@ END fi setcap cap_net_admin=ep /opt/nekoray/nekobox_core -chmod +x /opt/nekoray/pkexec -chmod 0755 /opt/nekoray/pkexec update-desktop-database EOF diff --git a/main/NekoGui.cpp b/main/NekoGui.cpp index b483f05..59c0699 100644 --- a/main/NekoGui.cpp +++ b/main/NekoGui.cpp @@ -12,6 +12,9 @@ #ifdef Q_OS_WIN #include "sys/windows/guihelper.h" #else +#ifdef Q_OS_LINUX +#include +#endif #include #endif @@ -407,6 +410,8 @@ namespace NekoGui { return !username.trimmed().isEmpty() && !password.trimmed().isEmpty(); } + // System Utils + QString FindCoreAsset(const QString &name) { QStringList search{NekoGui::dataStore->v2ray_asset_dir}; search << QApplication::applicationDirPath(); @@ -428,15 +433,26 @@ namespace NekoGui { return {}; } + QString FindNekoBoxCoreRealPath() { + auto fn = QApplication::applicationDirPath() + "/nekobox_core"; + auto fi = QFileInfo(fn); + if (fi.isSymLink()) return fi.symLinkTarget(); + return fn; + } + short isAdminCache = -1; - bool isAdmin() { + // IsAdmin 主要判断:有无权限启动 Tun + bool IsAdmin() { if (isAdminCache >= 0) return isAdminCache; - auto admin = NekoGui::dataStore->flag_linux_run_core_as_admin; + bool admin = false; #ifdef Q_OS_WIN admin = Windows_IsInAdmin(); #else +#ifdef Q_OS_LINUX + admin |= Linux_GetCapString(FindNekoBoxCoreRealPath()).contains("cap_net_admin"); +#endif admin |= geteuid() == 0; #endif diff --git a/main/NekoGui.hpp b/main/NekoGui.hpp index d6e6db5..08f9672 100644 --- a/main/NekoGui.hpp +++ b/main/NekoGui.hpp @@ -12,7 +12,9 @@ namespace NekoGui { QString FindCoreAsset(const QString &name); - bool isAdmin(); + QString FindNekoBoxCoreRealPath(); + + bool IsAdmin(); } // namespace NekoGui #define IS_NEKO_BOX (NekoGui::coreType == NekoGui::CoreType::SING_BOX) diff --git a/main/NekoGui_DataStore.hpp b/main/NekoGui_DataStore.hpp index 78c1a12..8508b4f 100644 --- a/main/NekoGui_DataStore.hpp +++ b/main/NekoGui_DataStore.hpp @@ -86,7 +86,6 @@ namespace NekoGui { bool flag_many = false; bool flag_tray = false; bool flag_debug = false; - bool flag_linux_run_core_as_admin = false; bool flag_restart_tun_on = false; bool flag_reorder = false; diff --git a/main/main.cpp b/main/main.cpp index c8c8857..5524be0 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -90,7 +90,6 @@ int main(int argc, char* argv[]) { } if (NekoGui::dataStore->argv.contains("-tray")) NekoGui::dataStore->flag_tray = true; if (NekoGui::dataStore->argv.contains("-debug")) NekoGui::dataStore->flag_debug = true; - if (NekoGui::dataStore->argv.contains("-flag_linux_run_core_as_admin")) NekoGui::dataStore->flag_linux_run_core_as_admin = true; if (NekoGui::dataStore->argv.contains("-flag_restart_tun_on")) NekoGui::dataStore->flag_restart_tun_on = true; if (NekoGui::dataStore->argv.contains("-flag_reorder")) NekoGui::dataStore->flag_reorder = true; #ifdef NKR_CPP_USE_APPDATA diff --git a/res/linux_pkexec.sh b/res/linux_pkexec.sh deleted file mode 100644 index 9bfe24b..0000000 --- a/res/linux_pkexec.sh +++ /dev/null @@ -1,9 +0,0 @@ -set -e - -command -v pkexec >/dev/null 2>&1 || echo "[Warning] pkexec not found" -command -v pkill >/dev/null 2>&1 || echo "[Warning] pkill not found" - -BASEDIR="$(dirname -- "$(readlink -f -- "$0")")" - -pkexec --keep-cwd \ - bash "$BASEDIR"/linux_pkexec_root.sh $@ diff --git a/res/linux_pkexec_root.sh b/res/linux_pkexec_root.sh deleted file mode 100644 index f332ce6..0000000 --- a/res/linux_pkexec_root.sh +++ /dev/null @@ -1,9 +0,0 @@ -set -e - -if [ "$EUID" -ne 0 ]; then - echo "[Warning] Not running as root" -fi - -#echo $$ >$PID_FILE - -$@ diff --git a/sys/ExternalProcess.cpp b/sys/ExternalProcess.cpp index a0a4a94..2b00f35 100644 --- a/sys/ExternalProcess.cpp +++ b/sys/ExternalProcess.cpp @@ -52,14 +52,6 @@ namespace NekoGui_sys { MW_show_log_ext(tag, "External core starting: " + env.join(" ") + " " + program + " " + arguments.join(" ")); } - QProcess::setEnvironment(env); - - if (NekoGui::dataStore->flag_linux_run_core_as_admin && dynamic_cast(this) && program != "pkexec") { - arguments.prepend(program); - arguments.prepend("--keep-cwd"); - program = "pkexec"; - } - QProcess::setEnvironment(env); QProcess::start(program, arguments); } diff --git a/sys/linux/LinuxCap.cpp b/sys/linux/LinuxCap.cpp new file mode 100644 index 0000000..b5245b0 --- /dev/null +++ b/sys/linux/LinuxCap.cpp @@ -0,0 +1,33 @@ +#include "LinuxCap.h" + +#include + +#define EXIT_CODE(p) (p.exitStatus() == QProcess::NormalExit ? p.exitCode() : -1) + +QString Linux_GetCapString(const QString &path) { + QProcess p; + p.setProgram("getcap"); + p.setArguments({path}); + p.start(); + p.waitForFinished(500); + return p.readAllStandardOutput(); +} + +int Linux_Pkexec_SetCapString(const QString &path, const QString &cap) { + QProcess p; + p.setProgram("pkexec"); + p.setArguments({"setcap", cap, path}); + p.start(); + p.waitForFinished(-1); + return EXIT_CODE(p); +} + +bool Linux_HavePkexec() { + QProcess p; + p.setProgram("pkexec"); + p.setArguments({"--help"}); + p.setProcessChannelMode(QProcess::SeparateChannels); + p.start(); + p.waitForFinished(500); + return EXIT_CODE(p) == 0; +} diff --git a/sys/linux/LinuxCap.h b/sys/linux/LinuxCap.h new file mode 100644 index 0000000..1a05bef --- /dev/null +++ b/sys/linux/LinuxCap.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +QString Linux_GetCapString(const QString &path); + +int Linux_Pkexec_SetCapString(const QString &path, const QString &cap); + +bool Linux_HavePkexec(); diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index d293aeb..f8ca55b 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -30,6 +30,9 @@ #ifdef Q_OS_WIN #include "3rdparty/WinCommander.hpp" #else +#ifdef Q_OS_LINUX +#include "sys/linux/LinuxCap.h" +#endif #include #endif @@ -691,6 +694,7 @@ void MainWindow::on_menu_exit_triggered() { arguments.removeFirst(); arguments.removeAll("-tray"); arguments.removeAll("-flag_restart_tun_on"); + arguments.removeAll("-flag_reorder"); } auto isLauncher = qEnvironmentVariable("NKR_FROM_LAUNCHER") == "1"; if (isLauncher) arguments.prepend("--"); @@ -702,7 +706,6 @@ void MainWindow::on_menu_exit_triggered() { #ifdef Q_OS_WIN WinCommander::runProcessElevated(program, arguments, "", WinCommander::SW_NORMAL, false); #else - arguments << "-flag_linux_run_core_as_admin"; QProcess::startDetached(program, arguments); #endif } else { @@ -756,19 +759,28 @@ void MainWindow::neko_set_spmode_vpn(bool enable, bool save) { if (enable != NekoGui::dataStore->spmode_vpn) { if (enable) { if (IS_NEKO_BOX_INTERNAL_TUN) { - bool requestPermission = !NekoGui::isAdmin(); -#ifdef Q_OS_LINUX - if (requestPermission && QProcess::execute("pkexec", {"--help"}) != 0) { - MessageBoxWarning(software_name, "Please install \"pkexec\" first."); - neko_set_spmode_FAILED - } -#endif + bool requestPermission = !NekoGui::IsAdmin(); if (requestPermission) { +#ifdef Q_OS_LINUX + if (!Linux_HavePkexec()) { + MessageBoxWarning(software_name, "Please install \"pkexec\" first."); + neko_set_spmode_FAILED + } + auto ret = Linux_Pkexec_SetCapString(NekoGui::FindNekoBoxCoreRealPath(), "cap_net_admin=ep"); + if (ret == 0) { + this->exit_reason = 3; + on_menu_exit_triggered(); + } else { + MessageBoxWarning(software_name, "Setcap for Tun mode failed.\n\n1. You may canceled the dialog.\n2. You may be using an incompatible environment like AppImage."); + } +#endif +#ifdef Q_OS_WIN auto n = QMessageBox::warning(GetMessageBoxParent(), software_name, tr("Please run NekoBox as admin"), QMessageBox::Yes | QMessageBox::No); if (n == QMessageBox::Yes) { this->exit_reason = 3; on_menu_exit_triggered(); } +#endif neko_set_spmode_FAILED } } else { @@ -854,7 +866,7 @@ void MainWindow::refresh_status(const QString &traffic_update) { auto make_title = [=](bool isTray) { QStringList tt; - if (!isTray && NekoGui::isAdmin()) tt << "[Admin]"; + if (!isTray && NekoGui::IsAdmin()) tt << "[Admin]"; if (select_mode) tt << "[" + tr("Select") + "]"; if (!title_error.isEmpty()) tt << "[" + title_error + "]"; if (NekoGui::dataStore->spmode_vpn) tt << "[VPN]";