mirror of
https://github.com/chenx-dust/EZ4Connect.git
synced 2026-09-20 10:23:33 +08:00
431 lines
20 KiB
C++
431 lines
20 KiB
C++
#include <QMessageBox>
|
||
#include <QCheckBox>
|
||
#include <QDialogButtonBox>
|
||
#include <QInputDialog>
|
||
#include <QLabel>
|
||
#include <QLineEdit>
|
||
#include <QVBoxLayout>
|
||
#include <QApplication>
|
||
|
||
#include "mainwindow.h"
|
||
#include "ui_mainwindow.h"
|
||
#include "sudowindow/sudowindow.h"
|
||
#include "loginwindow/loginwindow.h"
|
||
#include "utils/utils.h"
|
||
#include "zjuconnectcontroller/zjuconnectcontroller.h"
|
||
|
||
void MainWindow::initZjuConnect()
|
||
{
|
||
if (zjuConnectController != nullptr)
|
||
{
|
||
return;
|
||
}
|
||
|
||
clearLog();
|
||
|
||
zjuConnectController = new ZjuConnectController(this);
|
||
resetZjuConnectUi();
|
||
|
||
// 连接服务器
|
||
connect(zjuConnectController, &ZjuConnectController::outputRead, this,
|
||
[&](const QString &output)
|
||
{
|
||
ui->logPlainTextEdit->appendPlainText(output.trimmed());
|
||
});
|
||
|
||
connect(zjuConnectController, &ZjuConnectController::error, this,
|
||
[&](ZJU_ERROR err)
|
||
{
|
||
if (zjuConnectError == ZJU_ERROR::NONE)
|
||
{
|
||
zjuConnectError = err;
|
||
}
|
||
});
|
||
|
||
connect(zjuConnectController, &ZjuConnectController::askSudoPass, this,
|
||
[&]()
|
||
{
|
||
if (zjuConnectController->savedSudoPassword)
|
||
{
|
||
if (zjuConnectController->enteredSudoPassword)
|
||
{
|
||
addLog("sudo 密码可能有误,不使用记住的密码");
|
||
zjuConnectController->savedSudoPassword = false;
|
||
zjuConnectController->sudoPassword.clear();
|
||
}
|
||
else
|
||
{
|
||
zjuConnectController->enteredSudoPassword = true;
|
||
emit WriteToProcess(zjuConnectController->sudoPassword.toUtf8() + "\n");
|
||
return;
|
||
}
|
||
}
|
||
sudoWindow = new SudoWindow(this);
|
||
connect(sudoWindow, &SudoWindow::sudo, this, [&](const QString &password, bool save)
|
||
{
|
||
if (password.isEmpty())
|
||
{
|
||
zjuConnectController->stop();
|
||
}
|
||
else
|
||
{
|
||
if (save)
|
||
{
|
||
zjuConnectController->savedSudoPassword = true;
|
||
zjuConnectController->sudoPassword = password;
|
||
}
|
||
zjuConnectController->enteredSudoPassword = true;
|
||
emit WriteToProcess(password.toUtf8() + "\n");
|
||
}
|
||
});
|
||
sudoWindow->show();
|
||
});
|
||
|
||
connect(zjuConnectController, &ZjuConnectController::graphCaptcha, this,
|
||
[&](const QString &graphFile) {
|
||
addLog("需要图形验证码");
|
||
graphCaptchaWindow = new GraphCaptchaWindow(this);
|
||
graphCaptchaWindow->setGraph(graphFile);
|
||
graphCaptchaWindow->show();
|
||
connect(graphCaptchaWindow, &GraphCaptchaWindow::finishCaptcha, this, [&](const QByteArray &captcha) {
|
||
addLog("图形验证码用户输入:" + captcha);
|
||
emit WriteToProcess(captcha + "\n");
|
||
});
|
||
});
|
||
|
||
connect(zjuConnectController, &ZjuConnectController::smsCode, this, [&](bool showSkipSecondaryAuthOption) {
|
||
addLog("需要短信验证码");
|
||
|
||
QDialog smsCodeDialog(this);
|
||
smsCodeDialog.setWindowTitle("短信验证码");
|
||
|
||
auto *dialogLayout = new QVBoxLayout(&smsCodeDialog);
|
||
dialogLayout->addWidget(new QLabel("请输入短信验证码:", &smsCodeDialog));
|
||
|
||
auto *smsCodeLineEdit = new QLineEdit(&smsCodeDialog);
|
||
dialogLayout->addWidget(smsCodeLineEdit);
|
||
|
||
auto *skipSecondaryAuthCheckBox = new QCheckBox("跳过以后的短信验证", &smsCodeDialog);
|
||
skipSecondaryAuthCheckBox->setVisible(showSkipSecondaryAuthOption);
|
||
dialogLayout->addWidget(skipSecondaryAuthCheckBox);
|
||
|
||
auto *buttonBox = new QDialogButtonBox(
|
||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &smsCodeDialog);
|
||
connect(buttonBox, &QDialogButtonBox::accepted, &smsCodeDialog, &QDialog::accept);
|
||
connect(buttonBox, &QDialogButtonBox::rejected, &smsCodeDialog, &QDialog::reject);
|
||
dialogLayout->addWidget(buttonBox);
|
||
|
||
QString smsCode;
|
||
bool skipSecondaryAuth = false;
|
||
if (smsCodeDialog.exec() == QDialog::Accepted)
|
||
{
|
||
smsCode = smsCodeLineEdit->text();
|
||
skipSecondaryAuth = showSkipSecondaryAuthOption && skipSecondaryAuthCheckBox->isChecked();
|
||
}
|
||
|
||
addLog("短信验证码用户输入:" + smsCode + (skipSecondaryAuth ? " (跳过以后的短信验证)" : ""));
|
||
QByteArray smsCodeInput = smsCode.toLocal8Bit();
|
||
if (skipSecondaryAuth)
|
||
{
|
||
smsCodeInput.prepend('$');
|
||
}
|
||
emit WriteToProcess(smsCodeInput + "\n");
|
||
});
|
||
|
||
connect(zjuConnectController, &ZjuConnectController::totpCode, this, [&]() {
|
||
addLog("需要 TOTP 验证码");
|
||
QString totp = QInputDialog::getText(this, "TOTP 验证码", "请输入 TOTP 验证码:");
|
||
addLog("TOTP 验证码用户输入:" + totp);
|
||
emit WriteToProcess(totp.toLocal8Bit() + "\n");
|
||
});
|
||
|
||
connect(zjuConnectController, &ZjuConnectController::ssoAuth, this, [&]() {
|
||
ssoLoginWebView = new SsoLoginWebView(this);
|
||
connect(ssoLoginWebView, &SsoLoginWebView::loginCompleted,
|
||
[=](const QString &url) { emit WriteToProcess(url.toLocal8Bit() + "\n"); });
|
||
|
||
QString serverHost = settings->value("ZJUConnect/ServerAddress", "trust.hitsz.edu.cn").toString();
|
||
int serverPort = settings->value("ZJUConnect/ServerPort", 443).toInt();
|
||
if (serverPort != 443)
|
||
{
|
||
serverHost += ":" + QString::number(serverPort);
|
||
}
|
||
QString ssoUrl = settings->value("ZJUConnect/LoginURL").toString();
|
||
if (ssoUrl.isEmpty())
|
||
ssoUrl = "https://" + serverHost +
|
||
"/passport/v1/public/casLogin?sfDomain=" + settings->value("ZJUConnect/LoginDomain").toString();
|
||
if (ssoUrl.startsWith("/"))
|
||
ssoUrl = "https://" + serverHost + ssoUrl;
|
||
|
||
addLog(QStringLiteral("单点登录:") + ssoUrl);
|
||
ssoLoginWebView->setInitialUrl(QUrl::fromUserInput(ssoUrl));
|
||
ssoLoginWebView->setCallbackServerHost(serverHost);
|
||
ssoLoginWebView->show();
|
||
});
|
||
|
||
connect(zjuConnectController, &ZjuConnectController::finished, this, [&]()
|
||
{
|
||
addLog("VPN 断开!");
|
||
if (
|
||
(zjuConnectError == ZJU_ERROR::AUTH_EXPIRED || zjuConnectError == ZJU_ERROR::OTHER) &&
|
||
settings->value("Common/AutoReconnect", false).toBool() &&
|
||
isZjuConnectLinked
|
||
)
|
||
{
|
||
QTimer::singleShot(settings->value("Common/ReconnectTime", 1).toInt() * 1000, this, [&]()
|
||
{
|
||
if (isZjuConnectLinked)
|
||
{
|
||
addLog("正在尝试重新连接...");
|
||
zjuConnectController->stop();
|
||
|
||
isZjuConnectLinked = false;
|
||
isAutoReconnecting = true;
|
||
ui->pushButton1->click();
|
||
}
|
||
});
|
||
|
||
return;
|
||
}
|
||
|
||
if (zjuConnectError != ZJU_ERROR::NONE)
|
||
{
|
||
showNotification("VPN", "VPN 意外断开!", QSystemTrayIcon::MessageIcon::Warning);
|
||
}
|
||
isZjuConnectLinked = false;
|
||
ui->pushButton1->setText("连接服务器");
|
||
trayConnectAction->setText("连接服务器");
|
||
if (isSystemProxySet)
|
||
{
|
||
ui->pushButton2->click();
|
||
}
|
||
ui->pushButton2->hide();
|
||
|
||
switch (zjuConnectError)
|
||
{
|
||
case ZJU_ERROR::INVALID_DETAIL:
|
||
QMessageBox::critical(this, "错误", "登录失败!\n请检查设置中的网络账号和密码是否设置正确。");
|
||
break;
|
||
case ZJU_ERROR::BRUTE_FORCE:
|
||
QMessageBox::critical(this, "错误", "登录失败!\n登录尝试过于频繁,IP 被风控,请稍后重试或换用 EasyConnect。");
|
||
break;
|
||
case ZJU_ERROR::OTHER_LOGIN_FAILED:
|
||
QMessageBox::critical(this, "错误", "登录失败!\n未知原因,可将日志反馈给开发者以便调查。");
|
||
break;
|
||
case ZJU_ERROR::ACCESS_DENIED:
|
||
QMessageBox::critical(this, "错误", "权限不足!\n请关闭程序,点击右键以管理员身份运行。");
|
||
break;
|
||
case ZJU_ERROR::LISTEN_FAILED:
|
||
QMessageBox::critical(this, "错误", "监听失败!\n请关闭占用端口的程序(如残留的 zju-connect.exe),或者监听其它端口。");
|
||
break;
|
||
case ZJU_ERROR::CLIENT_FAILED:
|
||
QMessageBox::critical(this, "错误", "连接失败!\n可能是响应超时,请检查本地网络配置是否正常,服务器设置是否正确。");
|
||
break;
|
||
case ZJU_ERROR::CAPTCHA_FAILED:
|
||
QMessageBox::critical(this, "错误", "登录失败!\n验证码问题,可能是已验证码过期或者有误。");
|
||
break;
|
||
case ZJU_ERROR::PROGRAM_NOT_FOUND:
|
||
QMessageBox::critical(this, "错误", "程序未找到!\n请检查核心是否在正确路径下,检查是否解压在当前目录下。");
|
||
break;
|
||
case ZJU_ERROR::INTERACTIVE_ERROR:
|
||
QMessageBox::critical(this, "错误", "登录失败!\n请检查您的输入是否正确,检查是否完成 SSO 登录。");
|
||
break;
|
||
case ZJU_ERROR::AUTH_NOT_AVAILABLE:
|
||
QMessageBox::critical(this, "错误", "认证方式/登录域不可用!\n请通过“获取认证方式”按钮配置认证方式。");
|
||
break;
|
||
case ZJU_ERROR::AUTH_EXPIRED:
|
||
QMessageBox::critical(this, "错误", "认证已过期!\n请重新登录。");
|
||
break;
|
||
case ZJU_ERROR::OTHER:
|
||
QMessageBox::critical(this, "错误", "其它错误!\n未知原因,可将日志反馈给开发者以便调查。");
|
||
break;
|
||
case ZJU_ERROR::NONE:
|
||
default:
|
||
break;
|
||
}
|
||
zjuConnectError = ZJU_ERROR::NONE;
|
||
});
|
||
|
||
connect(ui->pushButton1, &QPushButton::clicked,
|
||
[&]()
|
||
{
|
||
if (!isZjuConnectLinked)
|
||
{
|
||
if (settings->contains("ZJUConnect/ServerAddress") &&
|
||
settings->value("ZJUConnect/ServerAddress").toString().isEmpty())
|
||
{
|
||
QMessageBox::critical(this, "错误", "服务器地址不能为空");
|
||
return;
|
||
}
|
||
|
||
QString username_ = settings->value("Credential/Username", "").toString();
|
||
QString password_ = QByteArray::fromBase64(settings->value("Credential/Password", "").toString().toUtf8());
|
||
QString protocol = settings->value("ZJUConnect/Protocol", "easyconnect").toString();
|
||
QString authtype = settings->value("ZJUConnect/AuthType", "psw").toString();
|
||
|
||
#if defined(Q_OS_WIN)
|
||
// Linux/macOS will elevate only the core process via sudo in the controller.
|
||
if (settings->value("ZJUConnect/TUNMode").toBool() && !Utils::isRunningAsAdmin())
|
||
{
|
||
if (Utils::relaunchAsAdmin())
|
||
{
|
||
QApplication::quit();
|
||
}
|
||
else
|
||
{
|
||
QMessageBox::warning(this, "提升失败", "无法以管理员权限重新启动,请手动以管理员方式运行。");
|
||
}
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
auto startZjuConnect = [this](const QString &username, const QString &password) {
|
||
QString program_path = Utils::getCorePath();
|
||
QString bind_prefix = settings->value("ZJUConnect/OutsideAccess", false).toBool() ? "[::]:" : "127.0.0.1:";
|
||
|
||
isZjuConnectLinked = true;
|
||
zjuConnectError = ZJU_ERROR::NONE;
|
||
ui->pushButton1->setText("断开服务器");
|
||
trayConnectAction->setText("断开服务器");
|
||
ui->pushButton2->show();
|
||
|
||
if (!isAutoReconnecting && settings->value("Common/AutoSetProxy", false).toBool())
|
||
{
|
||
ui->pushButton2->click();
|
||
}
|
||
isAutoReconnecting = false;
|
||
|
||
QString countryCode = settings->value("ZJUConnect/PhoneCountryCode").toString();
|
||
QString phoneNumber = settings->value("ZJUConnect/PhoneNumber").toString();
|
||
QString phone = !countryCode.isEmpty() && !phoneNumber.isEmpty() ? (countryCode + "-" + phoneNumber) : "";
|
||
|
||
zjuConnectController->start(
|
||
program_path, settings->value("ZJUConnect/Protocol").toString(),
|
||
settings->value("ZJUConnect/AuthType").toString(),
|
||
settings->value("ZJUConnect/LoginDomain").toString(), username, password, phone,
|
||
settings->value("Credential/TOTPSecret").toString(),
|
||
settings->value("ZJUConnect/ServerAddress").toString(),
|
||
settings->value("ZJUConnect/ServerPort").toInt(),
|
||
settings->value("ZJUConnect/DNS").toString(),
|
||
settings->value("ZJUConnect/DNSAuto").toBool(),
|
||
settings->value("ZJUConnect/SecondaryDNS").toString(),
|
||
settings->value("ZJUConnect/DNSTTL").toInt(),
|
||
bind_prefix + QString::number(settings->value("ZJUConnect/SOCKS5Port").toInt()),
|
||
bind_prefix + QString::number(settings->value("ZJUConnect/HTTPPort").toInt()),
|
||
settings->value("ZJUConnect/ShadowsocksURL").toString(),
|
||
settings->value("ZJUConnect/DialDirectProxy").toString(),
|
||
settings->value("ZJUConnect/UpdateBestNodesInterval", 300).toInt(),
|
||
!settings->value("ZJUConnect/MultiLine").toBool(),
|
||
!settings->value("ZJUConnect/KeepAlive").toBool(),
|
||
settings->value("ZJUConnect/KeepAliveURL", "").toString(),
|
||
settings->value("ZJUConnect/BindInterface", "").toString(),
|
||
settings->value("ZJUConnect/AutoDetectInterface", false).toBool(),
|
||
settings->value("ZJUConnect/SkipDomainResource").toBool(),
|
||
settings->value("ZJUConnect/DisableServerConfig").toBool(),
|
||
settings->value("ZJUConnect/ProxyAll").toBool(),
|
||
settings->value("ZJUConnect/DisableZJUDNS").toBool(),
|
||
!settings->value("ZJUConnect/ZJUDefault").toBool(),
|
||
settings->value("ZJUConnect/Debug").toBool(),
|
||
settings->value("ZJUConnect/TUNMode").toBool(),
|
||
settings->value("ZJUConnect/AddRoute").toBool(),
|
||
settings->value("ZJUConnect/DNSHijack").toBool(),
|
||
settings->value("ZJUConnect/FakeIP").toBool(),
|
||
settings->value("ZJUConnect/TCPTunnelMode").toBool(),
|
||
settings->value("ZJUConnect/TCPPortForwarding").toString(),
|
||
settings->value("ZJUConnect/UDPPortForwarding").toString(),
|
||
settings->value("ZJUConnect/CustomDNS", "").toString(),
|
||
settings->value("ZJUConnect/CustomProxyDomain", "").toString(),
|
||
settings->value("Credential/CertFile", "").toString(),
|
||
QByteArray::fromBase64(settings->value("Credential/CertPassword", "").toString().toUtf8()),
|
||
settings->value("ZJUConnect/ExtraArguments", "").toString(),
|
||
currentProfileId);
|
||
};
|
||
|
||
if (((protocol == "atrust" && authtype == "psw") ||
|
||
(protocol == "easyconnect" && settings->value("Credential/CertFile", "").toString().isEmpty())) &&
|
||
(username_.isEmpty() || password_.isEmpty()))
|
||
{
|
||
loginWindow = new LoginWindow(this);
|
||
loginWindow->setDetail(username_, password_);
|
||
|
||
connect(loginWindow, &LoginWindow::login, this,
|
||
[&, startZjuConnect](const QString& username, const QString& password, bool saveDetail)
|
||
{
|
||
if (saveDetail)
|
||
{
|
||
settings->setValue("Credential/Username", username);
|
||
settings->setValue("Credential/Password", QString(password.toUtf8().toBase64()));
|
||
settings->sync();
|
||
}
|
||
startZjuConnect(username, password);
|
||
}
|
||
);
|
||
loginWindow->show();
|
||
}
|
||
else
|
||
{
|
||
startZjuConnect(username_, password_);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
zjuConnectController->stop();
|
||
}
|
||
});
|
||
|
||
// 设置系统代理
|
||
connect(ui->pushButton2, &QPushButton::clicked,
|
||
[&]()
|
||
{
|
||
if (!isSystemProxySet)
|
||
{
|
||
int http_port = settings->value("ZJUConnect/HTTPPort").toInt();
|
||
int socks_port = settings->value("ZJUConnect/SOCKS5Port").toInt();
|
||
if (Utils::isSystemProxySet(http_port, socks_port))
|
||
{
|
||
bool suppressed = settings->value("Common/SuppressProxyOverrideWarning", false).toBool();
|
||
if (suppressed) {
|
||
addLog("跳过系统代理覆盖警告,因为已设置了不再提示");
|
||
} else {
|
||
QMessageBox msgBox(QMessageBox::Warning, "警告",
|
||
"当前已存在系统代理配置(可能是 Clash 或其它代理软件)\n是否覆盖当前系统代理配置?",
|
||
QMessageBox::Yes | QMessageBox::No, this);
|
||
|
||
QCheckBox *dontShowCheckBox = new QCheckBox("不再提示");
|
||
msgBox.setCheckBox(dontShowCheckBox);
|
||
|
||
if (msgBox.exec() == QMessageBox::No)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (dontShowCheckBox->isChecked())
|
||
{
|
||
settings->setValue("Common/SuppressProxyOverrideWarning", true);
|
||
settings->sync();
|
||
}
|
||
}
|
||
}
|
||
|
||
addLog("设置系统代理:HTTP端口 " + QString::number(http_port) + ",SOCKS5 端口 " + QString::number(socks_port));
|
||
Utils::setSystemProxy(settings->value("ZJUConnect/HTTPPort").toInt(),
|
||
settings->value("ZJUConnect/SOCKS5Port").toInt(),
|
||
settings->value("Common/SystemProxyBypass").toString());
|
||
ui->pushButton2->setText("清除系统代理");
|
||
isSystemProxySet = true;
|
||
}
|
||
else
|
||
{
|
||
Utils::clearSystemProxy();
|
||
ui->pushButton2->setText("设置系统代理");
|
||
isSystemProxySet = false;
|
||
if (!isZjuConnectLinked)
|
||
{
|
||
ui->pushButton2->hide();
|
||
}
|
||
}
|
||
});
|
||
|
||
emit SetModeFinished();
|
||
}
|