#include #include #include #include #include #include #include #include #include "mainwindow.h" #include "ui_mainwindow.h" #include "sudowindow/sudowindow.h" #include "loginwindow/loginwindow.h" #include "infrastructure/settingsprofileloader.h" #include "utils/utils.h" #include "zjuconnectcontroller/zjuconnectcontroller.h" void MainWindow::initZjuConnect() { if (connectionSession != nullptr) { return; } clearLog(); connectionSession = new ConnectionSession(this); resetZjuConnectUi(); // 连接服务器 connect(connectionSession, &ConnectionSession::outputRead, this, [&](const QString &output) { ui->logPlainTextEdit->appendPlainText(output.trimmed()); }); connect(connectionSession, &ConnectionSession::savedSudoPasswordRejected, this, [&]() { addLog("sudo 密码可能有误,不使用记住的密码"); }); connect(connectionSession, &ConnectionSession::askSudoPass, this, [&]() { sudoWindow = new SudoWindow(this); connect(sudoWindow, &SudoWindow::sudo, this, [&](const QString &password, bool save) { connectionSession->submitSudoPassword(password, save); }); sudoWindow->show(); }); connect(connectionSession, &ConnectionSession::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); connectionSession->submitInput(captcha + "\n"); }); }); connect(connectionSession, &ConnectionSession::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('$'); } connectionSession->submitInput(smsCodeInput + "\n"); }); connect(connectionSession, &ConnectionSession::totpCode, this, [&]() { addLog("需要 TOTP 验证码"); QString totp = QInputDialog::getText(this, "TOTP 验证码", "请输入 TOTP 验证码:"); addLog("TOTP 验证码用户输入:" + totp); connectionSession->submitInput(totp.toLocal8Bit() + "\n"); }); connect(connectionSession, &ConnectionSession::ssoAuth, this, [&]() { ssoLoginWebView = new SsoLoginWebView(this); connect(ssoLoginWebView, &SsoLoginWebView::loginCompleted, [=](const QString &url) { connectionSession->submitInput(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(connectionSession, &ConnectionSession::reconnectScheduled, this, [&](int) { addLog("正在尝试重新连接..."); }); connect(connectionSession, &ConnectionSession::finished, this, [&](ZJU_ERROR error) { addLog("VPN 断开!"); if (error != ZJU_ERROR::NONE) { showNotification("VPN", "VPN 意外断开!", QSystemTrayIcon::MessageIcon::Warning); } ui->pushButton1->setText("连接服务器"); trayConnectAction->setText("连接服务器"); if (systemProxySession->isEnabled()) { ui->pushButton2->click(); } ui->pushButton2->hide(); switch (error) { 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; } }); connect(ui->pushButton1, &QPushButton::clicked, [&]() { if (!connectionSession->isActive()) { 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) { ui->pushButton1->setText("断开服务器"); trayConnectAction->setText("断开服务器"); ui->pushButton2->show(); if (settings->value("Common/AutoSetProxy", false).toBool()) { ui->pushButton2->click(); } ConnectionProfile profile = SettingsProfileLoader::load(*settings, currentProfileId, username, password); profile.program = Utils::getCorePath(); const ReconnectPolicy reconnectPolicy{ settings->value("Common/AutoReconnect", false).toBool(), settings->value("Common/ReconnectTime", 1).toInt() * 1000 }; connectionSession->start(profile, reconnectPolicy); }; 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 { connectionSession->stop(); } }); // 设置系统代理 connect(ui->pushButton2, &QPushButton::clicked, [&]() { if (systemProxySession->isBusy()) { return; } if (!systemProxySession->isEnabled()) { int http_port = settings->value("ZJUConnect/HTTPPort").toInt(); int socks_port = settings->value("ZJUConnect/SOCKS5Port").toInt(); const SystemProxyConfig proxyConfig{ http_port, socks_port, settings->value("Common/SystemProxyBypass").toString() }; connect(systemProxySession, &SystemProxySession::conflictCheckFinished, this, [this, proxyConfig, http_port, socks_port](bool conflict) { if (!connectionSession->isActive()) { return; } if (conflict) { 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) ); systemProxySession->enable(proxyConfig); }, Qt::SingleShotConnection); systemProxySession->checkConflict(proxyConfig); } else { systemProxySession->disable(); } }); emit SetModeFinished(); }