#include #include #include #include #include #include #include "INIReader.h" #include "gpservice.h" #include "gpserviceadaptor.h" GPService::GPService(QObject *parent) : QObject(parent) , openconnect(new QProcess) { // Register the DBus service new GPServiceAdaptor(this); QDBusConnection dbus = QDBusConnection::systemBus(); dbus.registerObject("/", this); dbus.registerService("com.yuezk.qt.GPService"); // Setup the openconnect process QObject::connect(openconnect, &QProcess::started, this, &GPService::onProcessStarted); QObject::connect(openconnect, &QProcess::errorOccurred, this, &GPService::onProcessError); QObject::connect(openconnect, &QProcess::readyReadStandardOutput, this, &GPService::onProcessStdout); QObject::connect(openconnect, &QProcess::readyReadStandardError, this, &GPService::onProcessStderr); QObject::connect(openconnect, QOverload::of(&QProcess::finished), this, &GPService::onProcessFinished); } GPService::~GPService() { delete openconnect; } QString GPService::findBinary() { for (int i = 0; i < binaryPaths->length(); i++) { if (QFileInfo::exists(binaryPaths[i])) { return binaryPaths[i]; } } return nullptr; } QString GPService::extraOpenconnectArgs(const QString &gateway) { INIReader reader("/etc/gpservice/gp.conf"); if (reader.ParseError() < 0) { return ""; } std::string defaultArgs = reader.Get("*", "openconnect-args", ""); std::string extraArgs = reader.Get(gateway.toStdString(), "openconnect-args", defaultArgs); return QString::fromStdString(extraArgs); } /* Port from https://github.com/qt/qtbase/blob/11d1dcc6e263c5059f34b44d531c9ccdf7c0b1d6/src/corelib/io/qprocess.cpp#L2115 */ QStringList GPService::splitCommand(const QString &command) { QStringList args; QString tmp; int quoteCount = 0; bool inQuote = false; // handle quoting. tokens can be surrounded by double quotes // "hello world". three consecutive double quotes represent // the quote character itself. for (int i = 0; i < command.size(); ++i) { if (command.at(i) == QLatin1Char('"')) { ++quoteCount; if (quoteCount == 3) { // third consecutive quote quoteCount = 0; tmp += command.at(i); } continue; } if (quoteCount) { if (quoteCount == 1) inQuote = !inQuote; quoteCount = 0; } if (!inQuote && command.at(i).isSpace()) { if (!tmp.isEmpty()) { args += tmp; tmp.clear(); } } else { tmp += command.at(i); } } if (!tmp.isEmpty()) args += tmp; return args; } void GPService::quit() { if (openconnect->state() == QProcess::NotRunning) { exit(0); } else { aboutToQuit = true; openconnect->terminate(); } } void GPService::connect(QString server, QString username, QString passwd) { if (vpnStatus != GPService::VpnNotConnected) { log("VPN status is: " + QVariant::fromValue(vpnStatus).toString()); return; } QString bin = findBinary(); if (bin == nullptr) { log("Could not find openconnect binary, make sure openconnect is installed, exiting."); emit error("The OpenConect CLI was not found, make sure it has been installed!"); return; } if (!isValidVersion(bin)) { return; } const QString extraArgs = extraOpenconnectArgs(server); log(QString("Got extra OpenConnect args for server: %1, %2").arg(server, extraArgs.isEmpty() ? "" : extraArgs)); QStringList args; args << QCoreApplication::arguments().mid(1) << "--protocol=gp" << splitCommand(extraArgs) << "-u" << username << "--cookie-on-stdin" << server; log("Start process with arugments: " + args.join(", ")); openconnect->start(bin, args); openconnect->write((passwd + "\n").toUtf8()); } bool GPService::isValidVersion(QString &bin) { QProcess p; p.start(bin, QStringList("--version")); p.waitForFinished(); QString output = p.readAllStandardError() + p.readAllStandardOutput(); QRegularExpression re("v(\\d+).*?(\\s|\\n)"); QRegularExpressionMatch match = re.match(output); if (match.hasMatch()) { log("Output of `openconnect --version`: " + output); QString fullVersion = match.captured(0); QString majorVersion = match.captured(1); if (majorVersion.toInt() < 8) { emit error("The OpenConnect version must greater than v8.0.0, got " + fullVersion); return false; } } else { log("Failed to parse the OpenConnect version from " + output); } return true; } void GPService::disconnect() { if (openconnect->state() != QProcess::NotRunning) { vpnStatus = GPService::VpnDisconnecting; openconnect->terminate(); } } int GPService::status() { return vpnStatus; } void GPService::onProcessStarted() { log("Openconnect started successfully, PID=" + QString::number(openconnect->processId())); vpnStatus = GPService::VpnConnecting; } void GPService::onProcessError(QProcess::ProcessError error) { log("Error occurred: " + QVariant::fromValue(error).toString()); vpnStatus = GPService::VpnNotConnected; emit disconnected(); } void GPService::onProcessStdout() { QString output = openconnect->readAllStandardOutput(); log(output); if (output.indexOf("Connected as") >= 0 || output.indexOf("Configured as") >= 0) { vpnStatus = GPService::VpnConnected; emit connected(); } } void GPService::onProcessStderr() { log(openconnect->readAllStandardError()); } void GPService::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { log("Openconnect process exited with code " + QString::number(exitCode) + " and exit status " + QVariant::fromValue(exitStatus).toString()); vpnStatus = GPService::VpnNotConnected; emit disconnected(); if (aboutToQuit) { exit(0); }; } void GPService::log(QString msg) { emit logAvailable(msg); }