mirror of
https://github.com/chenx-dust/EZ4Connect.git
synced 2026-09-20 10:23:33 +08:00
559 lines
14 KiB
C++
559 lines
14 KiB
C++
#include "zjuconnectcontroller.h"
|
|
#include "mainwindow.h"
|
|
#include "utils/utils.h"
|
|
#include <qcontainerfwd.h>
|
|
#include <QCoreApplication>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QStandardPaths>
|
|
|
|
ZjuConnectController::ZjuConnectController(QWidget* parent) : QObject(parent)
|
|
{
|
|
zjuConnectProcess = new QProcess(this);
|
|
|
|
// 初始化日志文件
|
|
logFile = new QFile(Utils::getLogFilePath());
|
|
if (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
|
|
{
|
|
logStream = new QTextStream(logFile);
|
|
logStream->setEncoding(QStringConverter::Utf8);
|
|
QString startMsg = "=== Log started at " + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
|
|
" with " + QApplication::applicationDisplayName() + " " + QApplication::applicationVersion() +
|
|
" ===\n";
|
|
*logStream << startMsg;
|
|
logStream->flush();
|
|
}
|
|
|
|
auto outputProcess = [&](const QString& output)
|
|
{
|
|
emit outputRead(output);
|
|
|
|
// 写入日志文件
|
|
if (logStream != nullptr)
|
|
{
|
|
*logStream << output;
|
|
if (!output.endsWith('\n'))
|
|
{
|
|
*logStream << '\n';
|
|
}
|
|
logStream->flush();
|
|
}
|
|
|
|
if (output.contains("SUDO_ASK_PASS"))
|
|
{
|
|
emit askSudoPass();
|
|
}
|
|
else if (output.contains("Graph check code saved to "))
|
|
{
|
|
emit graphCaptcha(graphFile);
|
|
}
|
|
else if (output.contains("Please enter the SMS verification code: "))
|
|
{
|
|
emit smsCode(true);
|
|
}
|
|
else if (output.contains("Please enter your SMS code:"))
|
|
{
|
|
emit smsCode(false);
|
|
}
|
|
else if (output.contains("Please enter your TOTP code:"))
|
|
{
|
|
emit totpCode();
|
|
}
|
|
else if (output.contains("Please enter the callback url:"))
|
|
{
|
|
emit ssoAuth();
|
|
}
|
|
else if (output.contains("graph check code still required after second login attempt"))
|
|
{
|
|
emit error(ZJU_ERROR::CAPTCHA_FAILED);
|
|
}
|
|
else if (output.contains("Access is denied."))
|
|
{
|
|
emit error(ZJU_ERROR::ACCESS_DENIED);
|
|
}
|
|
else if (output.contains("listen failed"))
|
|
{
|
|
emit error(ZJU_ERROR::LISTEN_FAILED);
|
|
}
|
|
else if (output.contains("Invalid username or password!") || output.contains("ticket is empty"))
|
|
{
|
|
emit error(ZJU_ERROR::INVALID_DETAIL);
|
|
}
|
|
else if (output.contains("You are trying brute-force login on this IP address."))
|
|
{
|
|
emit error(ZJU_ERROR::BRUTE_FORCE);
|
|
}
|
|
else if (output.contains("Login failed") || output.contains("too many login failures"))
|
|
{
|
|
emit error(ZJU_ERROR::OTHER_LOGIN_FAILED);
|
|
}
|
|
else if (output.contains("unexpected newline"))
|
|
{
|
|
emit error(ZJU_ERROR::INTERACTIVE_ERROR);
|
|
}
|
|
else if (output.contains("auth type/login domain combination not found"))
|
|
{
|
|
emit error(ZJU_ERROR::AUTH_NOT_AVAILABLE);
|
|
}
|
|
else if (output.contains("invalid SID") || output.contains("l3-tunnel tunnel auth failed:"))
|
|
{
|
|
emit error(ZJU_ERROR::AUTH_EXPIRED);
|
|
}
|
|
else if (output.contains("client setup error"))
|
|
{
|
|
emit error(ZJU_ERROR::CLIENT_FAILED);
|
|
}
|
|
else if (output.contains("panic"))
|
|
{
|
|
emit error(ZJU_ERROR::OTHER);
|
|
}
|
|
};
|
|
|
|
connect(zjuConnectProcess, &QProcess::readyReadStandardOutput, this, [&, outputProcess]()
|
|
{
|
|
QString output = Utils::consoleOutputToQString(zjuConnectProcess->readAllStandardOutput());
|
|
|
|
outputProcess(output);
|
|
});
|
|
|
|
connect(zjuConnectProcess, &QProcess::readyReadStandardError, this, [&, outputProcess]()
|
|
{
|
|
QString output = Utils::consoleOutputToQString(zjuConnectProcess->readAllStandardError());
|
|
|
|
outputProcess(output);
|
|
});
|
|
|
|
connect(zjuConnectProcess, &QProcess::errorOccurred, this, [&](QProcess::ProcessError err)
|
|
{
|
|
if (stopRequested)
|
|
{
|
|
return;
|
|
}
|
|
QString timeString = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
|
|
QString errorString = zjuConnectProcess->errorString();
|
|
emit outputRead(timeString + " 退出原因:" + errorString);
|
|
|
|
if (errorString.contains("No such file or directory") || errorString.contains("not found") || errorString.contains("找不到"))
|
|
{
|
|
emit outputRead(timeString + " 核心路径:" + zjuConnectProcess->program());
|
|
emit error(ZJU_ERROR::PROGRAM_NOT_FOUND);
|
|
}
|
|
});
|
|
|
|
connect(zjuConnectProcess, &QProcess::finished, this, [&]()
|
|
{
|
|
stopRequested = false;
|
|
QString timeString = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
|
|
emit outputRead(timeString + " 退出原因:" "进程已结束");
|
|
emit finished();
|
|
});
|
|
|
|
|
|
connect(qobject_cast<MainWindow *>(parent), &MainWindow::WriteToProcess, this,
|
|
[&](const QByteArray &data) { zjuConnectProcess->write(data); });
|
|
}
|
|
|
|
QString ZjuConnectController::copyCoreForAppImage(const QString &programPath)
|
|
{
|
|
#if defined(Q_OS_UNIX)
|
|
static QString cachedSourcePath;
|
|
static QString cachedTempPath;
|
|
|
|
if (!qEnvironmentVariableIsSet("APPIMAGE")) {
|
|
return programPath;
|
|
}
|
|
|
|
if (cachedSourcePath == programPath && !cachedTempPath.isEmpty() && QFileInfo::exists(cachedTempPath)) {
|
|
return cachedTempPath;
|
|
}
|
|
|
|
const QFileInfo sourceInfo(programPath);
|
|
if (!sourceInfo.exists() || !sourceInfo.isFile()) {
|
|
return programPath;
|
|
}
|
|
|
|
const QString tempRoot = QStandardPaths::writableLocation(QStandardPaths::TempLocation)
|
|
+ "/EZ4Connect-" + QString::number(QCoreApplication::applicationPid());
|
|
QDir().mkpath(tempRoot);
|
|
|
|
const QString tempPath = tempRoot + "/" + sourceInfo.fileName();
|
|
if (QFileInfo::exists(tempPath)) {
|
|
QFile::remove(tempPath);
|
|
}
|
|
|
|
if (!QFile::copy(programPath, tempPath)) {
|
|
return programPath;
|
|
}
|
|
|
|
QFile::setPermissions(tempPath,
|
|
QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner |
|
|
QFileDevice::ReadGroup | QFileDevice::ExeGroup |
|
|
QFileDevice::ReadOther | QFileDevice::ExeOther);
|
|
cachedSourcePath = programPath;
|
|
cachedTempPath = tempPath;
|
|
return tempPath;
|
|
#else
|
|
return programPath;
|
|
#endif
|
|
}
|
|
|
|
void ZjuConnectController::start(
|
|
const QString& program,
|
|
const QString& protocol,
|
|
const QString& authType,
|
|
const QString& loginDomain,
|
|
const QString& username,
|
|
const QString& password,
|
|
const QString& phone,
|
|
const QString& totpSecret,
|
|
const QString& server,
|
|
int port,
|
|
const QString& dns,
|
|
bool dnsAuto,
|
|
const QString& secondaryDns,
|
|
int dnsTtl,
|
|
const QString& socksBind,
|
|
const QString& httpBind,
|
|
const QString& shadowsocksUrl,
|
|
const QString& dialDirectProxy,
|
|
int updateBestNodesInterval,
|
|
bool disableMultiLine,
|
|
bool disableKeepAlive,
|
|
const QString& keepAliveUrl,
|
|
bool skipDomainResource,
|
|
bool disableServerConfig,
|
|
bool proxyAll,
|
|
bool disableZjuDns,
|
|
bool disableZjuConfig,
|
|
bool debugDump,
|
|
bool tunMode,
|
|
bool addRoute,
|
|
bool dnsHijack,
|
|
bool fakeIp,
|
|
bool tcpTunnelMode,
|
|
const QString& tcpPortForwarding,
|
|
const QString& udpPortForwarding,
|
|
const QString& customDNS,
|
|
const QString& customProxyDomain,
|
|
const QString& certFile,
|
|
const QString& certPassword,
|
|
const QString& extraArguments,
|
|
const QString& profileId
|
|
)
|
|
{
|
|
QStringList args;
|
|
|
|
if (!protocol.isEmpty())
|
|
{
|
|
args.append("-protocol");
|
|
args.append(protocol);
|
|
}
|
|
|
|
if (!authType.isEmpty())
|
|
{
|
|
args.append("-auth-type");
|
|
args.append("auth/" + authType);
|
|
}
|
|
|
|
if (protocol == "atrust")
|
|
{
|
|
// 图形验证码文件路径
|
|
if (tempDir == nullptr)
|
|
{
|
|
tempDir = new QTemporaryDir;
|
|
tempDir->setAutoRemove(true);
|
|
}
|
|
graphFile = tempDir->filePath("graph.jpg");
|
|
args.append("-graph-code-file");
|
|
args.append(graphFile);
|
|
|
|
// 存放 Client Data
|
|
args.append("-client-data-file");
|
|
args.append(Utils::getClientDataPath(profileId));
|
|
}
|
|
|
|
if (!phone.isEmpty())
|
|
{
|
|
args.append("-phone");
|
|
args.append(phone);
|
|
}
|
|
|
|
if (!loginDomain.isEmpty())
|
|
{
|
|
args.append("-login-domain");
|
|
args.append(loginDomain);
|
|
}
|
|
|
|
if (!server.isEmpty())
|
|
{
|
|
args.append("-server");
|
|
args.append(server);
|
|
}
|
|
|
|
if (port != 0)
|
|
{
|
|
args.append("-port");
|
|
args.append(QString::number(port));
|
|
}
|
|
|
|
if (!dns.isEmpty() || dnsAuto)
|
|
{
|
|
args.append("-zju-dns-server");
|
|
if (dnsAuto)
|
|
{
|
|
args.append("auto");
|
|
}
|
|
else
|
|
{
|
|
args.append(dns);
|
|
}
|
|
}
|
|
|
|
if (dnsTtl != 3600)
|
|
{
|
|
args.append("-dns-ttl");
|
|
args.append(QString::number(dnsTtl));
|
|
}
|
|
|
|
if (!secondaryDns.isEmpty())
|
|
{
|
|
args.append("-secondary-dns-server");
|
|
args.append(secondaryDns);
|
|
}
|
|
|
|
if (disableMultiLine)
|
|
{
|
|
args.append("-disable-multi-line");
|
|
}
|
|
|
|
if (disableKeepAlive)
|
|
{
|
|
args.append("-disable-keep-alive");
|
|
}
|
|
|
|
if (!keepAliveUrl.isEmpty())
|
|
{
|
|
args.append("-keep-alive-url");
|
|
args.append(keepAliveUrl);
|
|
}
|
|
|
|
if (disableZjuConfig)
|
|
{
|
|
args.append("-disable-zju-config");
|
|
}
|
|
|
|
if (disableZjuDns)
|
|
{
|
|
args.append("-disable-zju-dns");
|
|
}
|
|
|
|
if (disableServerConfig)
|
|
{
|
|
args.append("-disable-server-config");
|
|
}
|
|
|
|
if (proxyAll)
|
|
{
|
|
args.append("-proxy-all");
|
|
}
|
|
|
|
if (skipDomainResource)
|
|
{
|
|
args.append("-skip-domain-resource");
|
|
}
|
|
|
|
if (tunMode)
|
|
{
|
|
args.append("-tun-mode");
|
|
|
|
if (dnsHijack)
|
|
{
|
|
args.append("-dns-hijack");
|
|
if (fakeIp)
|
|
{
|
|
args.append("-fake-ip");
|
|
}
|
|
}
|
|
|
|
if (addRoute)
|
|
{
|
|
args.append("-add-route");
|
|
}
|
|
}
|
|
|
|
if (tcpTunnelMode)
|
|
{
|
|
args.append("-tcp-tunnel-mode");
|
|
}
|
|
|
|
if (debugDump)
|
|
{
|
|
args.append("-debug-dump");
|
|
}
|
|
|
|
if (!socksBind.isEmpty())
|
|
{
|
|
args.append("-socks-bind");
|
|
args.append(socksBind);
|
|
}
|
|
|
|
if (!httpBind.isEmpty())
|
|
{
|
|
args.append("-http-bind");
|
|
args.append(httpBind);
|
|
}
|
|
|
|
if (!shadowsocksUrl.isEmpty())
|
|
{
|
|
args.append("-shadowsocks-url");
|
|
args.append(shadowsocksUrl);
|
|
}
|
|
|
|
if (!dialDirectProxy.isEmpty())
|
|
{
|
|
args.append("-dial-direct-proxy");
|
|
args.append(dialDirectProxy);
|
|
}
|
|
|
|
if (updateBestNodesInterval != 300)
|
|
{
|
|
args.append("-update-best-nodes-interval");
|
|
args.append(QString::number(updateBestNodesInterval));
|
|
}
|
|
|
|
if (!tcpPortForwarding.isEmpty())
|
|
{
|
|
args.append("-tcp-port-forwarding");
|
|
args.append(tcpPortForwarding);
|
|
}
|
|
|
|
if (!udpPortForwarding.isEmpty())
|
|
{
|
|
args.append("-udp-port-forwarding");
|
|
args.append(udpPortForwarding);
|
|
}
|
|
|
|
if (!customDNS.isEmpty())
|
|
{
|
|
args.append("-custom-dns");
|
|
args.append(customDNS);
|
|
}
|
|
|
|
if (!customProxyDomain.isEmpty())
|
|
{
|
|
args.append("-custom-proxy-domain");
|
|
args.append(customProxyDomain);
|
|
}
|
|
|
|
if (!extraArguments.isEmpty())
|
|
{
|
|
args.append(extraArguments.split(" "));
|
|
}
|
|
|
|
QString timeString = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
|
|
emit outputRead(timeString + " VPN 启动!参数:" + args.join(' '));
|
|
|
|
QStringList credentialList;
|
|
|
|
if (!username.isEmpty())
|
|
{
|
|
credentialList.append("-username");
|
|
credentialList.append(username);
|
|
}
|
|
|
|
if (!password.isEmpty())
|
|
{
|
|
credentialList.append("-password");
|
|
credentialList.append(password);
|
|
}
|
|
|
|
if (!totpSecret.isEmpty())
|
|
{
|
|
emit outputRead(timeString + " 使用了 TOTP");
|
|
credentialList.append("-totp-secret");
|
|
credentialList.append(totpSecret);
|
|
}
|
|
|
|
if (!certFile.isEmpty())
|
|
{
|
|
emit outputRead(timeString + " 使用了证书文件");
|
|
args.append("-cert-file");
|
|
args.append(certFile);
|
|
}
|
|
|
|
if (!certPassword.isEmpty())
|
|
{
|
|
args.append("-cert-password");
|
|
args.append(certPassword);
|
|
}
|
|
|
|
QString programToStart = program;
|
|
QStringList finalArgs = credentialList + args;
|
|
|
|
#if defined(Q_OS_UNIX)
|
|
if (tunMode && !Utils::isRunningAsAdmin())
|
|
{
|
|
programToStart = copyCoreForAppImage(programToStart);
|
|
|
|
QStringList sudoArgs;
|
|
sudoArgs << "-p" << "SUDO_ASK_PASS";
|
|
sudoArgs << "-S";
|
|
sudoArgs << programToStart << finalArgs;
|
|
programToStart = "sudo";
|
|
finalArgs = sudoArgs;
|
|
enteredSudoPassword = false;
|
|
}
|
|
#endif
|
|
|
|
zjuConnectProcess->start(programToStart, finalArgs);
|
|
zjuConnectProcess->waitForStarted();
|
|
if (zjuConnectProcess->state() == QProcess::NotRunning)
|
|
{
|
|
emit finished();
|
|
}
|
|
}
|
|
|
|
void ZjuConnectController::stop()
|
|
{
|
|
if (zjuConnectProcess->state() == QProcess::NotRunning)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!stopRequested)
|
|
{
|
|
stopRequested = true;
|
|
zjuConnectProcess->terminate();
|
|
}
|
|
else
|
|
{
|
|
zjuConnectProcess->kill();
|
|
}
|
|
}
|
|
|
|
ZjuConnectController::~ZjuConnectController()
|
|
{
|
|
stop();
|
|
|
|
// 关闭日志文件
|
|
if (logStream != nullptr)
|
|
{
|
|
QString endMsg = "=== Log ended at " + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") + " ===\n";
|
|
*logStream << endMsg;
|
|
logStream->flush();
|
|
delete logStream;
|
|
logStream = nullptr;
|
|
}
|
|
|
|
if (logFile != nullptr)
|
|
{
|
|
if (logFile->isOpen())
|
|
{
|
|
logFile->close();
|
|
}
|
|
delete logFile;
|
|
logFile = nullptr;
|
|
}
|
|
}
|