mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +07:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ff589f47c2 | |||
| c66d8ecca0 | |||
| db535f7e7d | |||
| 89f30d8c31 | |||
| 8bce432824 | |||
| f3539b2632 | |||
| 7a96c212f3 | |||
| 2d5dc54e0f | |||
| cef4c262e9 | |||
| 34309261a8 | |||
| 657eeb40c7 | |||
| b4938c2cc9 | |||
| 524fefc5cb | |||
| 73f13404bb | |||
| f0ca9772d3 | |||
| 5fc68cca83 | |||
| fcb7b8fa8d | |||
| a81e32ff95 | |||
| 23b530a0f8 | |||
| c897052107 | |||
| 4d0efc7ea5 | |||
| a77842c9e3 | |||
| 0ded9db780 | |||
| 58d480fcb5 | |||
| 7154428d26 | |||
| 02a52d0169 | |||
| ec60764072 | |||
| 17d2fa5532 | |||
| 3ca8b534e8 | |||
| e88f7c5e46 | |||
| 511b8fa6cc | |||
| 3ac5d7bd1f | |||
| 19cad00a00 | |||
| 1ea716a163 | |||
| 4551659c2a | |||
| c568bf8c24 | |||
| a412d91105 | |||
| ad01f23bbe | |||
| 656070b132 | |||
| c907f5ca36 | |||
| 94a13b2b54 | |||
| 169f11d9c7 | |||
| 816dc3af95 | |||
| b802863de5 | |||
| 8dc2a4b76c | |||
| beb1c6dbf2 | |||
| 3eb06916c7 | |||
| 30d0f84a4f | |||
| 251f2aa5db | |||
| 38082f9940 | |||
| 71691fa01e | |||
| 62d9bcaf7f | |||
| 5ef8254cba | |||
| f767171c06 | |||
| eff460b227 | |||
| 319043818a | |||
| e730521576 | |||
| 517930dd22 | |||
| 26994c21b1 | |||
| 681eb5aa86 | |||
| 4b86425992 | |||
| 9b41ed66bb | |||
| 8bb4fa3f35 | |||
| e792117be1 |
+2
-2
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.8.9.2)
|
||||
set(AMNEZIAVPN_VERSION 4.8.11.0)
|
||||
|
||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2092)
|
||||
set(APP_ANDROID_VERSION_CODE 2095)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
+1
-1
Submodule client/3rd-prebuilt updated: 02a00ee00d...efad1a5b5c
Vendored
+1
-1
Submodule client/3rd/amneziawg-apple updated: 811af0a83b...76e7db556a
@@ -25,7 +25,9 @@
|
||||
#include <QtQuick/QQuickWindow> // for QQuickWindow
|
||||
#include <QWindow> // for qobject_cast<QWindow*>
|
||||
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
|
||||
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
|
||||
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs"))
|
||||
{
|
||||
setQuitOnLastWindowClosed(false);
|
||||
|
||||
@@ -51,18 +53,8 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
|
||||
|
||||
AmneziaApplication::~AmneziaApplication()
|
||||
{
|
||||
if (m_vpnConnection) {
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteLater", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
m_vpnConnectionThread.quit();
|
||||
|
||||
if (!m_vpnConnectionThread.wait(5000)) {
|
||||
m_vpnConnectionThread.terminate();
|
||||
m_vpnConnectionThread.wait();
|
||||
}
|
||||
|
||||
if (m_engine) {
|
||||
QObject::disconnect(m_engine, 0, 0, 0);
|
||||
delete m_engine;
|
||||
@@ -119,7 +111,7 @@ void AmneziaApplication::init()
|
||||
Logger::setServiceLogsEnabled(enabled);
|
||||
|
||||
#ifdef Q_OS_WIN //TODO
|
||||
if (m_parser.isSet("a"))
|
||||
if (m_parser.isSet(m_optAutostart))
|
||||
m_coreController->pageController()->showOnStartup();
|
||||
else
|
||||
emit m_coreController->pageController()->raiseMainWindow();
|
||||
@@ -187,15 +179,12 @@ bool AmneziaApplication::parseCommands()
|
||||
m_parser.addHelpOption();
|
||||
m_parser.addVersionOption();
|
||||
|
||||
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
|
||||
m_parser.addOption(c_autostart);
|
||||
|
||||
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
|
||||
m_parser.addOption(c_cleanup);
|
||||
m_parser.addOption(m_optAutostart);
|
||||
m_parser.addOption(m_optCleanup);
|
||||
|
||||
m_parser.process(*this);
|
||||
|
||||
if (m_parser.isSet(c_cleanup)) {
|
||||
if (m_parser.isSet(m_optCleanup)) {
|
||||
Logger::cleanUp();
|
||||
QTimer::singleShot(100, this, [this] { quit(); });
|
||||
exec();
|
||||
|
||||
@@ -56,6 +56,9 @@ private:
|
||||
|
||||
QCommandLineParser m_parser;
|
||||
|
||||
QCommandLineOption m_optAutostart;
|
||||
QCommandLineOption m_optCleanup;
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
|
||||
)
|
||||
|
||||
if(NOT IOS AND NOT MACOS_NE)
|
||||
@@ -86,7 +85,6 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||
)
|
||||
|
||||
if(NOT IOS AND NOT MACOS_NE)
|
||||
@@ -189,11 +187,13 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h
|
||||
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
|
||||
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
|
||||
${CLIENT_ROOT_DIR}/core/privileged_process.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace
|
||||
|
||||
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
|
||||
return endDate < now;
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
||||
|
||||
initNotificationHandler();
|
||||
|
||||
auto locale = m_settings->getAppLanguage();
|
||||
m_translator.reset(new QTranslator());
|
||||
updateTranslator(locale);
|
||||
updateTranslator(m_settings->getAppLanguage());
|
||||
}
|
||||
|
||||
void CoreController::initModels()
|
||||
@@ -154,9 +153,6 @@ void CoreController::initControllers()
|
||||
|
||||
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
||||
|
||||
m_transferController.reset(new TransferController(m_settings, m_serversModel, m_exportController.get()));
|
||||
m_engine->rootContext()->setContextProperty("TransferController", m_transferController.get());
|
||||
}
|
||||
|
||||
void CoreController::initAndroidController()
|
||||
@@ -236,7 +232,7 @@ void CoreController::initSignalHandlers()
|
||||
|
||||
void CoreController::initNotificationHandler()
|
||||
{
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||
|
||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "ui/systemtray_notificationhandler.h"
|
||||
#endif
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "ui/controllers/settingsController.h"
|
||||
#include "ui/controllers/sitesController.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
#include "ui/controllers/transferController.h"
|
||||
|
||||
#include "ui/models/allowed_dns_model.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
@@ -49,7 +48,7 @@
|
||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "ui/notificationhandler.h"
|
||||
#endif
|
||||
|
||||
@@ -98,7 +97,7 @@ private:
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QSharedPointer<QTranslator> m_translator;
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||
#endif
|
||||
|
||||
@@ -146,7 +145,6 @@ private:
|
||||
#endif
|
||||
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
||||
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
||||
QScopedPointer<TransferController> m_transferController;
|
||||
};
|
||||
|
||||
#endif // CORECONTROLLER_H
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrl>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "QBlockCipher.h"
|
||||
#include "QRsa.h"
|
||||
@@ -61,8 +60,9 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||
|
||||
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
|
||||
request.setUrl(QString(endpoint).arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
@@ -123,8 +123,9 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||
|
||||
request.setUrl(endpoint.arg(m_gatewayEndpoint));
|
||||
request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
@@ -345,11 +346,14 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QByteArray responseBody;
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl, QNetworkReply *reply,
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
|
||||
qDebug() << "go to the next proxy endpoint";
|
||||
reply->deleteLater(); // delete the previous reply
|
||||
reply = requestFunction(endpoint.arg(proxyUrl));
|
||||
@@ -359,6 +363,50 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl
|
||||
wait.exec();
|
||||
|
||||
if (replyProcessingFunction(reply, sslErrors)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (m_proxyUrl.isEmpty()) {
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(1000);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
request.setUrl(proxyUrl + "lmbd-health");
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
reply->deleteLater();
|
||||
|
||||
m_proxyUrl = proxyUrl;
|
||||
if (!m_proxyUrl.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_proxyUrl.isEmpty()) {
|
||||
if (bypassFunction(endpoint, m_proxyUrl, reply, requestFunction, replyProcessingFunction)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
if (bypassFunction(endpoint, proxyUrl, reply, requestFunction, replyProcessingFunction)) {
|
||||
m_proxyUrl = proxyUrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ private:
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isDevEnvironment = false;
|
||||
bool m_isStrictKillSwitchEnabled = false;
|
||||
|
||||
inline static QString m_proxyUrl;
|
||||
};
|
||||
|
||||
#endif // GATEWAYCONTROLLER_H
|
||||
|
||||
@@ -197,12 +197,8 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
|
||||
return error;
|
||||
}
|
||||
|
||||
QTemporaryFile localFile;
|
||||
localFile.open();
|
||||
localFile.write(data);
|
||||
localFile.close();
|
||||
|
||||
error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
|
||||
// Write directly via SCP without creating a temporary local file.
|
||||
error = m_sshClient.scpWriteBuffer(overwriteMode, data, remotePath, "non_desc");
|
||||
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
|
||||
@@ -120,6 +120,7 @@ namespace amnezia
|
||||
ApiNotFoundError = 1109,
|
||||
ApiMigrationError = 1110,
|
||||
ApiUpdateRequestError = 1111,
|
||||
ApiSubscriptionExpiredError = 1112,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
|
||||
@@ -77,6 +77,7 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
||||
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
|
||||
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
|
||||
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
|
||||
|
||||
// QFile errors
|
||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||
|
||||
@@ -18,6 +18,12 @@ bool IpcClient::isSocketConnected() const
|
||||
return m_isSocketConnected;
|
||||
}
|
||||
|
||||
void IpcClient::close()
|
||||
{
|
||||
if (m_localSocket)
|
||||
m_localSocket->close();
|
||||
}
|
||||
|
||||
IpcClient *IpcClient::Instance()
|
||||
{
|
||||
return m_instance;
|
||||
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
|
||||
|
||||
bool isSocketConnected() const;
|
||||
void close();
|
||||
|
||||
signals:
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
const uint32_t S_IRWXU = 0644;
|
||||
@@ -290,6 +291,54 @@ namespace libssh {
|
||||
return watcher.result();
|
||||
}
|
||||
|
||||
ErrorCode Client::scpWriteBuffer(const ScpOverwriteMode overwriteMode, const QByteArray &data, const QString &remotePath, const QString &fileDesc)
|
||||
{
|
||||
m_scpSession = ssh_scp_new(m_session, SSH_SCP_WRITE, remotePath.toStdString().c_str());
|
||||
|
||||
if (m_scpSession == nullptr) {
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
|
||||
if (ssh_scp_init(m_scpSession) != SSH_OK) {
|
||||
auto errorCode = fromLibsshErrorCode();
|
||||
closeScpSession();
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QFutureWatcher<ErrorCode> watcher;
|
||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::scpWriteBufferFinished);
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &data, &remotePath, &fileDesc]() {
|
||||
const int accessType = O_WRONLY | O_CREAT | overwriteMode;
|
||||
const int totalSize = data.size();
|
||||
|
||||
int result = ssh_scp_push_file(m_scpSession, remotePath.toStdString().c_str(), totalSize, accessType);
|
||||
if (result != SSH_OK) {
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
|
||||
constexpr int bufferSize = 16384;
|
||||
int transferred = 0;
|
||||
while (transferred < totalSize) {
|
||||
const int chunkSize = std::min(bufferSize, totalSize - transferred);
|
||||
result = ssh_scp_write(m_scpSession, data.constData() + transferred, chunkSize);
|
||||
if (result != SSH_OK) {
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
transferred += chunkSize;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
});
|
||||
watcher.setFuture(future);
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &Client::scpWriteBufferFinished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
closeScpSession();
|
||||
return watcher.result();
|
||||
}
|
||||
|
||||
void Client::closeScpSession()
|
||||
{
|
||||
if (m_scpSession != nullptr) {
|
||||
|
||||
@@ -36,6 +36,11 @@ namespace libssh {
|
||||
const QString &localPath,
|
||||
const QString &remotePath,
|
||||
const QString &fileDesc);
|
||||
// Copy data directly without a temporary local file
|
||||
ErrorCode scpWriteBuffer(const ScpOverwriteMode overwriteMode,
|
||||
const QByteArray &data,
|
||||
const QString &remotePath,
|
||||
const QString &fileDesc);
|
||||
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback);
|
||||
private:
|
||||
ErrorCode closeChannel();
|
||||
@@ -52,6 +57,7 @@ namespace libssh {
|
||||
signals:
|
||||
void writeToChannelFinished();
|
||||
void scpFileCopyFinished();
|
||||
void scpWriteBufferFinished();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -101,10 +101,10 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||
out << "MTU = " << m_deviceMTU << "\n";
|
||||
}
|
||||
|
||||
if (!m_primaryDnsServer.isNull()) {
|
||||
if (!m_primaryDnsServer.isEmpty()) {
|
||||
QStringList dnsServers;
|
||||
dnsServers.append(m_primaryDnsServer);
|
||||
if (!m_secondaryDnsServer.isNull()) {
|
||||
if (!m_secondaryDnsServer.isEmpty()) {
|
||||
dnsServers.append(m_secondaryDnsServer);
|
||||
}
|
||||
// If the DNS is not the Gateway, it's a user defined DNS
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include <QMap>
|
||||
#include "ipaddress.h"
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<!-- Monitor -->
|
||||
<rect x="2.5" y="3.5" width="14" height="10" rx="2" ry="2" stroke="#D7D8DB" stroke-width="1.8"/>
|
||||
<line x1="9.5" y1="13.5" x2="9.5" y2="16.5" stroke="#D7D8DB" stroke-width="1.8" stroke-linecap="round"/>
|
||||
<rect x="6" y="16.5" width="7" height="1.8" rx="0.9" ry="0.9" fill="#D7D8DB"/>
|
||||
|
||||
<!-- Phone -->
|
||||
<rect x="17.5" y="6" width="4.5" height="9.5" rx="1.2" ry="1.2" stroke="#D7D8DB" stroke-width="1.8"/>
|
||||
<circle cx="19.75" cy="14.2" r="0.6" fill="#D7D8DB"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 628 B |
@@ -32,17 +32,41 @@
|
||||
<false/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>AmneziaVPNLaunchScreen</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<true/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>QIOSWindowSceneDelegate</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array/>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array/>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Light</string>
|
||||
<key>com.wireguard.ios.app_group_id</key>
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QHostAddress>
|
||||
@@ -12,12 +15,13 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QLocalSocket>
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
|
||||
#include "ipaddress.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "models/server.h"
|
||||
#include "daemon/daemonerrors.h"
|
||||
|
||||
#include "protocols/protocols_defs.h"
|
||||
@@ -115,7 +119,6 @@ void LocalSocketController::daemonConnected() {
|
||||
}
|
||||
|
||||
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
|
||||
QString protocolName = rawConfig.value("protocol").toString();
|
||||
|
||||
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
|
||||
@@ -132,13 +135,16 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
||||
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
||||
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
||||
m_deviceIpv4 = wgConfig.value(amnezia::config_key::client_ip).toString();
|
||||
|
||||
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
|
||||
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
|
||||
// Otherwise some OSes (Linux) try IPv6 forever and hang.
|
||||
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
|
||||
// https://man7.org/linux/man-pages/man5/gai.conf.5.html
|
||||
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
|
||||
|
||||
// simply "dead::1" is globally-routable, don't use it
|
||||
json.insert("deviceIpv6Address", "fd58:baa6:dead::1");
|
||||
|
||||
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
||||
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
|
||||
@@ -220,7 +226,6 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
|
||||
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
||||
|
||||
|
||||
QJsonArray jsExcludedAddresses;
|
||||
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
|
||||
if (splitTunnelType == 2) {
|
||||
@@ -264,13 +269,13 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
|
||||
// && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
|
||||
// && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
|
||||
/* && !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
|
||||
@@ -278,27 +283,27 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()) {
|
||||
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()*/) {
|
||||
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
||||
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
||||
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
||||
json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize));
|
||||
json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize));
|
||||
json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
|
||||
json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize));
|
||||
// json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
|
||||
// json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize));
|
||||
json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
|
||||
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
|
||||
json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2));
|
||||
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
||||
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
||||
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
||||
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
||||
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
||||
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
||||
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
||||
// json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
|
||||
// json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2));
|
||||
// json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
||||
// json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
||||
// json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
||||
// json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
||||
// json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
||||
// json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
||||
// json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
||||
}
|
||||
|
||||
write(json);
|
||||
@@ -449,6 +454,7 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
||||
}
|
||||
|
||||
if (type == "status") {
|
||||
|
||||
QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway");
|
||||
if (!serverIpv4Gateway.isString()) {
|
||||
logger.error() << "Unexpected serverIpv4Gateway value";
|
||||
@@ -493,6 +499,11 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
||||
|
||||
logger.debug() << "Handshake completed with:"
|
||||
<< pubkey.toString();
|
||||
|
||||
checkStatus();
|
||||
|
||||
emit statusUpdated("", m_deviceIpv4, 0, 0);
|
||||
|
||||
emit connected(pubkey.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "controllerimpl.h"
|
||||
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
class LocalSocketController final : public ControllerImpl {
|
||||
@@ -58,6 +59,7 @@ class LocalSocketController final : public ControllerImpl {
|
||||
|
||||
QByteArray m_buffer;
|
||||
|
||||
QString m_deviceIpv4;
|
||||
std::function<void(const QString&)> m_logCallback = nullptr;
|
||||
|
||||
QTimer m_initializingTimer;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "logger.h"
|
||||
//#include "mozillavpn.h"
|
||||
#include "networkwatcherimpl.h"
|
||||
#include "platforms/dummy/dummynetworkwatcher.h"
|
||||
//#include "settingsholder.h"
|
||||
|
||||
#ifdef MZ_WINDOWS
|
||||
@@ -51,7 +50,7 @@ NetworkWatcher::NetworkWatcher() { MZ_COUNT_CTOR(NetworkWatcher); }
|
||||
NetworkWatcher::~NetworkWatcher() { MZ_COUNT_DTOR(NetworkWatcher); }
|
||||
|
||||
void NetworkWatcher::initialize() {
|
||||
logger.debug() << "Initialize";
|
||||
logger.debug() << "Initialize NetworkWatcher";
|
||||
|
||||
#if defined(MZ_WINDOWS)
|
||||
m_impl = new WindowsNetworkWatcher(this);
|
||||
@@ -69,59 +68,45 @@ void NetworkWatcher::initialize() {
|
||||
m_impl = new DummyNetworkWatcher(this);
|
||||
#endif
|
||||
|
||||
|
||||
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
|
||||
&NetworkWatcher::unsecuredNetwork);
|
||||
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
|
||||
&NetworkWatcher::networkChange);
|
||||
|
||||
connect(m_impl, &NetworkWatcherImpl::sleepMode, this,
|
||||
&NetworkWatcher::onSleepMode);
|
||||
m_impl->initialize();
|
||||
|
||||
|
||||
// TODO: IMPL FOR AMNEZIA
|
||||
#if 0
|
||||
SettingsHolder* settingsHolder = SettingsHolder::instance();
|
||||
Q_ASSERT(settingsHolder);
|
||||
|
||||
m_active = settingsHolder->unsecuredNetworkAlert() ||
|
||||
settingsHolder->captivePortalAlert();
|
||||
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
|
||||
if (m_active) {
|
||||
// Enable sleep/wake monitoring for VPN auto-reconnection
|
||||
logger.debug() << "Starting NetworkWatcher for sleep/wake monitoring";
|
||||
logger.debug() << "About to call m_impl->start()";
|
||||
try {
|
||||
m_impl->start();
|
||||
logger.debug() << "m_impl->start() completed successfully";
|
||||
} catch (const std::exception& e) {
|
||||
logger.error() << "Exception in m_impl->start():" << e.what();
|
||||
} catch (...) {
|
||||
logger.error() << "Unknown exception in m_impl->start()";
|
||||
}
|
||||
|
||||
connect(settingsHolder, &SettingsHolder::unsecuredNetworkAlertChanged, this,
|
||||
&NetworkWatcher::settingsChanged);
|
||||
connect(settingsHolder, &SettingsHolder::captivePortalAlertChanged, this,
|
||||
&NetworkWatcher::settingsChanged);
|
||||
|
||||
#endif
|
||||
m_active = true;
|
||||
m_reportUnsecuredNetwork = false; // Disable unsecured network alerts for Amnezia
|
||||
}
|
||||
|
||||
void NetworkWatcher::settingsChanged() {
|
||||
// TODO: IMPL FOR AMNEZIA
|
||||
#if 0
|
||||
SettingsHolder* settingsHolder = SettingsHolder::instance();
|
||||
m_active = settingsHolder->unsecuredNetworkAlert() ||
|
||||
settingsHolder->captivePortalAlert();
|
||||
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
|
||||
// For Amnezia: Keep NetworkWatcher always active for sleep/wake monitoring
|
||||
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
|
||||
}
|
||||
|
||||
if (m_active) {
|
||||
logger.debug()
|
||||
<< "Starting Network Watcher; Reporting of Unsecured Networks: "
|
||||
<< m_reportUnsecuredNetwork;
|
||||
m_impl->start();
|
||||
} else {
|
||||
logger.debug() << "Stopping Network Watcher";
|
||||
m_impl->stop();
|
||||
}
|
||||
#endif
|
||||
void NetworkWatcher::onSleepMode()
|
||||
{
|
||||
logger.debug() << "Resumed from sleep mode";
|
||||
emit sleepMode();
|
||||
}
|
||||
|
||||
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
||||
const QString& networkId) {
|
||||
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
|
||||
<< "id:" << logger.sensitive(networkId);
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
if (!m_reportUnsecuredNetwork) {
|
||||
logger.debug() << "Disabled. Ignoring unsecured network";
|
||||
|
||||
@@ -29,10 +29,13 @@ public:
|
||||
// false to restore.
|
||||
void simulateDisconnection(bool simulatedDisconnection);
|
||||
|
||||
void onSleepMode();
|
||||
|
||||
QNetworkInformation::Reachability getReachability();
|
||||
|
||||
signals:
|
||||
void networkChange();
|
||||
void sleepMode();
|
||||
|
||||
private:
|
||||
void settingsChanged();
|
||||
|
||||
@@ -41,6 +41,8 @@ signals:
|
||||
// TODO: Only windows-networkwatcher has this, the other plattforms should
|
||||
// too.
|
||||
void networkChanged(QString newBSSID);
|
||||
void sleepMode();
|
||||
|
||||
|
||||
private:
|
||||
bool m_active = false;
|
||||
|
||||
@@ -41,6 +41,7 @@ void PingHelper::start(const QString& serverIpv4Gateway,
|
||||
|
||||
m_gateway = QHostAddress(serverIpv4Gateway);
|
||||
m_source = QHostAddress(deviceIpv4Address.section('/', 0, 0));
|
||||
|
||||
m_pingSender = PingSenderFactory::create(m_source, this);
|
||||
|
||||
// Some platforms require root access to send and receive ICMP pings. If
|
||||
@@ -53,8 +54,10 @@ void PingHelper::start(const QString& serverIpv4Gateway,
|
||||
|
||||
connect(m_pingSender, &PingSender::recvPing, this, &PingHelper::pingReceived,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_pingSender, &PingSender::criticalPingError, this,
|
||||
[]() { logger.info() << "Encountered Unrecoverable ping error"; });
|
||||
connect(m_pingSender, &PingSender::criticalPingError, this, [this]() {
|
||||
logger.info() << "Encountered Unrecoverable ping error";
|
||||
emit connectionLose();
|
||||
});
|
||||
|
||||
// Reset the ping statistics
|
||||
m_sequence = 0;
|
||||
|
||||
@@ -33,6 +33,8 @@ class PingHelper final : public QObject {
|
||||
|
||||
signals:
|
||||
void pingSentAndReceived(qint64 msec);
|
||||
void connectionLose();
|
||||
|
||||
|
||||
private:
|
||||
void nextPing();
|
||||
|
||||
@@ -5,27 +5,26 @@
|
||||
#include "pingsenderfactory.h"
|
||||
|
||||
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
|
||||
//# include "platforms/linux/linuxpingsender.h"
|
||||
# include "platforms/linux/linuxpingsender.h"
|
||||
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
||||
# include "platforms/macos/macospingsender.h"
|
||||
# include "platforms/macos/macospingsender.h"
|
||||
#elif defined(MZ_WINDOWS)
|
||||
# include "platforms/windows/windowspingsender.h"
|
||||
#elif defined(MZ_DUMMY) || defined(UNIT_TEST)
|
||||
# include "platforms/dummy/dummypingsender.h"
|
||||
# include "platforms/windows/windowspingsender.h"
|
||||
#elif defined(MZ_WASM) || defined(UNIT_TEST)
|
||||
# include "platforms/dummy/dummypingsender.h"
|
||||
#else
|
||||
# error "Unsupported platform"
|
||||
# error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
PingSender* PingSenderFactory::create(const QHostAddress& source,
|
||||
QObject* parent) {
|
||||
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
|
||||
return nullptr;
|
||||
// return new LinuxPingSender(source, parent);
|
||||
return new LinuxPingSender(source, parent);
|
||||
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
||||
return new MacOSPingSender(source, parent);
|
||||
return new MacOSPingSender(source, parent);
|
||||
#elif defined(MZ_WINDOWS)
|
||||
return new WindowsPingSender(source, parent);
|
||||
return new WindowsPingSender(source, parent);
|
||||
#else
|
||||
return new DummyPingSender(source, parent);
|
||||
return new DummyPingSender(source, parent);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -10,9 +10,10 @@ class QHostAddress;
|
||||
class QObject;
|
||||
|
||||
class PingSenderFactory final {
|
||||
public:
|
||||
PingSenderFactory() = delete;
|
||||
static PingSender* create(const QHostAddress& source, QObject* parent);
|
||||
public:
|
||||
PingSenderFactory() = delete;
|
||||
static PingSender* create(const QHostAddress& source, QObject* parent);
|
||||
};
|
||||
|
||||
|
||||
#endif // PINGSENDERFACTORY_H
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <objc/runtime.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QFile>
|
||||
#include <QString>
|
||||
|
||||
#include "ios_controller.h"
|
||||
|
||||
using SceneOpenURLContexts = void (*)(id, SEL, UIScene *, NSSet<UIOpenURLContext *> *);
|
||||
|
||||
static SceneOpenURLContexts g_originalSceneOpenURLContexts = nullptr;
|
||||
|
||||
static void amnezia_handleURL(NSURL *url)
|
||||
{
|
||||
if (!url || !url.isFileURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString filePath(url.path.UTF8String);
|
||||
if (filePath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if (filePath.contains("backup")) {
|
||||
IosController::Instance()->importBackupFromOutside(filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray data = file.readAll();
|
||||
IosController::Instance()->importConfigFromOutside(QString::fromUtf8(data));
|
||||
});
|
||||
}
|
||||
|
||||
static void amnezia_scene_openURLContexts(id self, SEL _cmd, UIScene *scene, NSSet<UIOpenURLContext *> *contexts)
|
||||
{
|
||||
if (g_originalSceneOpenURLContexts) {
|
||||
g_originalSceneOpenURLContexts(self, _cmd, scene, contexts);
|
||||
}
|
||||
|
||||
if (!contexts || contexts.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
for (UIOpenURLContext *context in contexts) {
|
||||
amnezia_handleURL(context.URL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@interface AmneziaSceneDelegateHooks : NSObject
|
||||
@end
|
||||
|
||||
@implementation AmneziaSceneDelegateHooks
|
||||
|
||||
+ (void)load
|
||||
{
|
||||
Class cls = objc_getClass("QIOSWindowSceneDelegate");
|
||||
if (!cls) {
|
||||
return;
|
||||
}
|
||||
|
||||
SEL selector = @selector(scene:openURLContexts:);
|
||||
Method method = class_getInstanceMethod(cls, selector);
|
||||
if (method) {
|
||||
g_originalSceneOpenURLContexts = reinterpret_cast<SceneOpenURLContexts>(method_getImplementation(method));
|
||||
method_setImplementation(method, reinterpret_cast<IMP>(amnezia_scene_openURLContexts));
|
||||
} else {
|
||||
const char *types = "v@:@@";
|
||||
class_addMethod(cls, selector, reinterpret_cast<IMP>(amnezia_scene_openURLContexts), types);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -29,12 +29,46 @@ const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
||||
|
||||
#if !MACOS_NE
|
||||
static UIViewController* getViewController() {
|
||||
NSArray *windows = [[UIApplication sharedApplication]windows];
|
||||
for (UIWindow *window in windows) {
|
||||
if (window.isKeyWindow) {
|
||||
UIApplication *application = [UIApplication sharedApplication];
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
for (UIScene *scene in application.connectedScenes) {
|
||||
if (scene.activationState != UISceneActivationStateForegroundActive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (![scene isKindOfClass:[UIWindowScene class]]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
||||
|
||||
for (UIWindow *window in windowScene.windows) {
|
||||
if (window.isKeyWindow && window.rootViewController) {
|
||||
return window.rootViewController;
|
||||
}
|
||||
}
|
||||
|
||||
for (UIWindow *window in windowScene.windows) {
|
||||
if (!window.isHidden && window.rootViewController) {
|
||||
return window.rootViewController;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (UIWindow *window in application.windows) {
|
||||
if (window.isKeyWindow && window.rootViewController) {
|
||||
return window.rootViewController;
|
||||
}
|
||||
}
|
||||
|
||||
for (UIWindow *window in application.windows) {
|
||||
if (window.rootViewController) {
|
||||
return window.rootViewController;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -34,6 +34,9 @@ void IOSNetworkWatcher::initialize() {
|
||||
});
|
||||
nw_path_monitor_start(m_networkMonitor);
|
||||
|
||||
// Call start() to initialize sleep/wake monitoring (will call MacOSNetworkWatcher::start() if this is macOS)
|
||||
this->start();
|
||||
|
||||
//TODO IMPL FOR AMNEZIA
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ void LinuxNetworkWatcher::initialize() {
|
||||
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
|
||||
&LinuxNetworkWatcher::unsecuredNetwork);
|
||||
|
||||
connect(m_worker, &LinuxNetworkWatcherWorker::sleepMode, this,
|
||||
&NetworkWatcherImpl::sleepMode);
|
||||
|
||||
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
|
||||
// This is not strictly needed, but it's better for user experience because
|
||||
// it makes the UI faster to appear, plus it gives a bit of delay between the
|
||||
|
||||
@@ -33,7 +33,21 @@
|
||||
#define NM_802_11_AP_SEC_WEAK_CRYPTO \
|
||||
(NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104)
|
||||
|
||||
|
||||
enum NMState {
|
||||
NM_STATE_UNKNOWN = 0,
|
||||
NM_STATE_ASLEEP = 10,
|
||||
NM_STATE_DISCONNECTED = 20,
|
||||
NM_STATE_DISCONNECTING = 30,
|
||||
NM_STATE_CONNECTING = 40,
|
||||
NM_STATE_CONNECTED_LOCAL = 50,
|
||||
NM_STATE_CONNECTED_SITE = 60,
|
||||
NM_STATE_CONNECTED_GLOBAL = 70
|
||||
};
|
||||
|
||||
|
||||
constexpr const char* DBUS_NETWORKMANAGER = "org.freedesktop.NetworkManager";
|
||||
constexpr const char* DBUS_NETWORKMANAGER_PATH = "/org/freedesktop/NetworkManager";
|
||||
|
||||
namespace {
|
||||
Logger logger("LinuxNetworkWatcherWorker");
|
||||
@@ -73,7 +87,7 @@ void LinuxNetworkWatcherWorker::initialize() {
|
||||
// documentation:
|
||||
// https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html
|
||||
|
||||
QDBusInterface nm(DBUS_NETWORKMANAGER, "/org/freedesktop/NetworkManager",
|
||||
QDBusInterface nm(DBUS_NETWORKMANAGER, DBUS_NETWORKMANAGER_PATH,
|
||||
DBUS_NETWORKMANAGER, QDBusConnection::systemBus());
|
||||
if (!nm.isValid()) {
|
||||
logger.error()
|
||||
@@ -108,6 +122,12 @@ void LinuxNetworkWatcherWorker::initialize() {
|
||||
SLOT(propertyChanged(QString, QVariantMap, QStringList)));
|
||||
}
|
||||
|
||||
QDBusConnection::systemBus().connect(DBUS_NETWORKMANAGER,
|
||||
DBUS_NETWORKMANAGER_PATH,
|
||||
DBUS_NETWORKMANAGER,
|
||||
"StateChanged",
|
||||
this, SLOT(NMStateChanged(quint32)));
|
||||
|
||||
if (m_devicePaths.isEmpty()) {
|
||||
logger.warning() << "No wifi devices found";
|
||||
return;
|
||||
@@ -173,5 +193,16 @@ void LinuxNetworkWatcherWorker::checkDevices() {
|
||||
emit unsecuredNetwork(ssid, bssid);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
|
||||
{
|
||||
if (state == NM_STATE_ASLEEP) {
|
||||
emit sleepMode();
|
||||
}
|
||||
|
||||
logger.debug() << "NMStateChanged " << state;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
|
||||
|
||||
signals:
|
||||
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
||||
void sleepMode();
|
||||
|
||||
public slots:
|
||||
void initialize();
|
||||
@@ -30,6 +31,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
|
||||
private slots:
|
||||
void propertyChanged(QString interface, QVariantMap properties,
|
||||
QStringList list);
|
||||
void NMStateChanged(quint32 state);
|
||||
|
||||
private:
|
||||
// We collect the list of DBus wifi network device paths during the
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "linuxpingsender.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <linux/filter.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip_icmp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <QSocketNotifier>
|
||||
#include <QtEndian>
|
||||
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "qhostaddress.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("LinuxPingSender");
|
||||
}
|
||||
|
||||
int LinuxPingSender::createSocket() {
|
||||
// Try creating an ICMP socket. This would be the ideal choice, but it can
|
||||
// fail depending on the kernel config (see: sys.net.ipv4.ping_group_range)
|
||||
m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
|
||||
if (m_socket >= 0) {
|
||||
m_ident = 0;
|
||||
return m_socket;
|
||||
}
|
||||
if ((errno != EPERM) && (errno != EACCES)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// As a fallback, create a raw socket, which requires root permissions
|
||||
// or CAP_NET_RAW to be granted to the VPN client.
|
||||
m_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
|
||||
if (m_socket < 0) {
|
||||
return -1;
|
||||
}
|
||||
m_ident = getpid() & 0xffff;
|
||||
|
||||
// Attach a BPF filter to discard everything but replies to our echo.
|
||||
struct sock_filter bpf_prog[] = {
|
||||
BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, 0), /* Skip IP header. */
|
||||
BPF_STMT(BPF_LD | BPF_H | BPF_IND, 4), /* Load icmp echo ident */
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, m_ident, 1, 0), /* Ours? */
|
||||
BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected identifier. Reject. */
|
||||
BPF_STMT(BPF_LD | BPF_B | BPF_IND, 0), /* Load icmp type */
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP_ECHOREPLY, 1, 0), /* Echo? */
|
||||
BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected type. Reject. */
|
||||
BPF_STMT(BPF_RET | BPF_K, ~0U), /* Packet passes the filter. */
|
||||
};
|
||||
struct sock_fprog filter = {
|
||||
.len = sizeof(bpf_prog) / sizeof(struct sock_filter),
|
||||
.filter = bpf_prog,
|
||||
};
|
||||
setsockopt(m_socket, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));
|
||||
|
||||
return m_socket;
|
||||
}
|
||||
|
||||
LinuxPingSender::LinuxPingSender(const QHostAddress& source, QObject* parent)
|
||||
: PingSender(parent) {
|
||||
MZ_COUNT_CTOR(LinuxPingSender);
|
||||
|
||||
logger.debug() << "LinuxPingSender(" + logger.sensitive(source.toString()) +
|
||||
") created";
|
||||
|
||||
m_socket = createSocket();
|
||||
if (m_socket < 0) {
|
||||
logger.error() << "Socket creation error: " << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
quint32 ipv4addr = INADDR_ANY;
|
||||
if (!source.isNull()) {
|
||||
ipv4addr = source.toIPv4Address();
|
||||
}
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof addr);
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = qToBigEndian<quint32>(ipv4addr);
|
||||
|
||||
if (bind(m_socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
|
||||
close(m_socket);
|
||||
m_socket = -1;
|
||||
logger.error() << "bind error:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
m_notifier = new QSocketNotifier(m_socket, QSocketNotifier::Read, this);
|
||||
if (m_ident) {
|
||||
connect(m_notifier, &QSocketNotifier::activated, this,
|
||||
&LinuxPingSender::rawSocketReady);
|
||||
} else {
|
||||
connect(m_notifier, &QSocketNotifier::activated, this,
|
||||
&LinuxPingSender::icmpSocketReady);
|
||||
}
|
||||
}
|
||||
|
||||
LinuxPingSender::~LinuxPingSender() {
|
||||
MZ_COUNT_DTOR(LinuxPingSender);
|
||||
if (m_socket >= 0) {
|
||||
close(m_socket);
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
|
||||
quint32 ipv4dest = dest.toIPv4Address();
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = qToBigEndian<quint32>(ipv4dest);
|
||||
|
||||
struct icmphdr packet;
|
||||
memset(&packet, 0, sizeof(packet));
|
||||
packet.type = ICMP_ECHO;
|
||||
packet.un.echo.id = htons(m_ident);
|
||||
packet.un.echo.sequence = htons(sequence);
|
||||
packet.checksum = inetChecksum(&packet, sizeof(packet));
|
||||
|
||||
int rc = sendto(m_socket, &packet, sizeof(packet), 0, (struct sockaddr*)&addr,
|
||||
sizeof(addr));
|
||||
if (rc < 0) {
|
||||
logger.error() << "failed to send:" << strerror(errno);
|
||||
if (errno == ENETUNREACH) {
|
||||
emit criticalPingError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxPingSender::icmpSocketReady() {
|
||||
socklen_t slen = 0;
|
||||
unsigned char data[2048];
|
||||
int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen);
|
||||
if (rc <= 0) {
|
||||
logger.error() << "recvfrom failed:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
struct icmphdr packet;
|
||||
if (rc >= (int)sizeof(packet)) {
|
||||
memcpy(&packet, data, sizeof(packet));
|
||||
if (packet.type == ICMP_ECHOREPLY) {
|
||||
emit recvPing(htons(packet.un.echo.sequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxPingSender::rawSocketReady() {
|
||||
socklen_t slen = 0;
|
||||
unsigned char data[2048];
|
||||
int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen);
|
||||
if (rc <= 0) {
|
||||
logger.error() << "recvfrom failed:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the IP header
|
||||
const struct iphdr* ip = (struct iphdr*)data;
|
||||
int iphdrlen = ip->ihl * 4;
|
||||
if (rc < iphdrlen || iphdrlen < (int)sizeof(struct iphdr)) {
|
||||
logger.error() << "malformed IP packet:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the ICMP packet
|
||||
struct icmphdr packet;
|
||||
if (inetChecksum(data + iphdrlen, rc - iphdrlen) != 0) {
|
||||
logger.warning() << "invalid checksum";
|
||||
return;
|
||||
}
|
||||
if (rc >= (iphdrlen + (int)sizeof(packet))) {
|
||||
memcpy(&packet, data + iphdrlen, sizeof(packet));
|
||||
quint16 id = htons(m_ident);
|
||||
if ((packet.type == ICMP_ECHOREPLY) && (packet.un.echo.id == id)) {
|
||||
emit recvPing(htons(packet.un.echo.sequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LINUXPINGSENDER_H
|
||||
#define LINUXPINGSENDER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "../client/mozilla/pingsender.h"
|
||||
|
||||
class QSocketNotifier;
|
||||
|
||||
class LinuxPingSender final : public PingSender {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(LinuxPingSender)
|
||||
|
||||
public:
|
||||
LinuxPingSender(const QHostAddress& source, QObject* parent = nullptr);
|
||||
~LinuxPingSender();
|
||||
|
||||
bool isValid() override { return (m_socket >= 0); };
|
||||
|
||||
void sendPing(const QHostAddress& dest, quint16 sequence) override;
|
||||
|
||||
private:
|
||||
int createSocket();
|
||||
|
||||
private slots:
|
||||
void rawSocketReady();
|
||||
void icmpSocketReady();
|
||||
|
||||
private:
|
||||
QSocketNotifier* m_notifier = nullptr;
|
||||
int m_socket = -1;
|
||||
quint16 m_ident = 0;
|
||||
};
|
||||
|
||||
#endif // LINUXPINGSENDER_H
|
||||
@@ -10,8 +10,31 @@
|
||||
#include "../ios/iosnetworkwatcher.h"
|
||||
#include "networkwatcherimpl.h"
|
||||
|
||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#include <IOKit/IOMessage.h>
|
||||
|
||||
|
||||
class QString;
|
||||
|
||||
// Inspired by https://ladydebug.com/blog/2020/05/21/programmatically-capture-energy-saver-event-on-mac/
|
||||
class PowerNotificationsListener
|
||||
{
|
||||
public:
|
||||
PowerNotificationsListener(class MacOSNetworkWatcher* watcher) : m_watcher(watcher) {}
|
||||
void registerForNotifications();
|
||||
void cleanup();
|
||||
|
||||
private:
|
||||
static void sleepWakeupCallBack(void *refParam, io_service_t service, natural_t messageType, void *messageArgument);
|
||||
|
||||
private:
|
||||
class MacOSNetworkWatcher* m_watcher = nullptr;
|
||||
IONotificationPortRef notifyPortRef = nullptr; // notification port allocated by IORegisterForSystemPower
|
||||
io_object_t notifierObj = IO_OBJECT_NULL; // notifier object, used to deregister later
|
||||
io_connect_t rootPowerDomain = IO_OBJECT_NULL; // a reference to the Root Power Domain IOService
|
||||
};
|
||||
|
||||
|
||||
class MacOSNetworkWatcher final : public IOSNetworkWatcher {
|
||||
public:
|
||||
MacOSNetworkWatcher(QObject* parent);
|
||||
@@ -25,6 +48,7 @@ class MacOSNetworkWatcher final : public IOSNetworkWatcher {
|
||||
|
||||
private:
|
||||
void* m_delegate = nullptr;
|
||||
PowerNotificationsListener m_powerlistener;
|
||||
};
|
||||
|
||||
#endif // MACOSNETWORKWATCHER_H
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QMetaObject>
|
||||
#include <pthread.h>
|
||||
#include <iostream>
|
||||
|
||||
#import <CoreWLAN/CoreWLAN.h>
|
||||
#import <Network/Network.h>
|
||||
|
||||
@@ -13,6 +18,37 @@ namespace {
|
||||
Logger logger("MacOSNetworkWatcher");
|
||||
}
|
||||
|
||||
// Global variables for CFRunLoop thread
|
||||
static pthread_t g_powerThread;
|
||||
static CFRunLoopRef g_powerRunLoop = nullptr;
|
||||
static bool g_shouldStopPowerThread = false;
|
||||
static PowerNotificationsListener* g_powerListener = nullptr;
|
||||
|
||||
// Thread function for dedicated CFRunLoop
|
||||
void* powerMonitoringThread(void* arg) {
|
||||
logger.debug() << "Power monitoring thread started";
|
||||
|
||||
PowerNotificationsListener* listener = static_cast<PowerNotificationsListener*>(arg);
|
||||
|
||||
// Get the runloop for this thread
|
||||
g_powerRunLoop = CFRunLoopGetCurrent();
|
||||
|
||||
// Register for power notifications in this thread
|
||||
listener->registerForNotifications();
|
||||
|
||||
// Run the CFRunLoop - this will block until CFRunLoopStop is called
|
||||
while (!g_shouldStopPowerThread) {
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
listener->cleanup();
|
||||
g_powerRunLoop = nullptr;
|
||||
|
||||
logger.debug() << "Power monitoring thread finished";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@interface MacOSNetworkWatcherDelegate : NSObject <CWEventDelegate> {
|
||||
MacOSNetworkWatcher* m_watcher;
|
||||
}
|
||||
@@ -38,12 +74,138 @@ Logger logger("MacOSNetworkWatcher");
|
||||
|
||||
@end
|
||||
|
||||
MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : IOSNetworkWatcher(parent) {
|
||||
void PowerNotificationsListener::registerForNotifications()
|
||||
{
|
||||
logger.debug() << "Registering for system power notifications in dedicated thread";
|
||||
|
||||
rootPowerDomain = IORegisterForSystemPower(this, ¬ifyPortRef, sleepWakeupCallBack, ¬ifierObj);
|
||||
if (rootPowerDomain == IO_OBJECT_NULL) {
|
||||
logger.error() << "Failed to register for system power notifications!";
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the notification port to the current runloop (dedicated thread)
|
||||
CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes);
|
||||
logger.debug() << "Power notifications registered successfully";
|
||||
}
|
||||
|
||||
void PowerNotificationsListener::cleanup()
|
||||
{
|
||||
if (notifyPortRef != nullptr) {
|
||||
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes);
|
||||
IONotificationPortDestroy(notifyPortRef);
|
||||
notifyPortRef = nullptr;
|
||||
}
|
||||
|
||||
if (notifierObj != IO_OBJECT_NULL) {
|
||||
IODeregisterForSystemPower(¬ifierObj);
|
||||
notifierObj = IO_OBJECT_NULL;
|
||||
}
|
||||
|
||||
if (rootPowerDomain != IO_OBJECT_NULL) {
|
||||
IOServiceClose(rootPowerDomain);
|
||||
rootPowerDomain = IO_OBJECT_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_service_t service, natural_t messageType, void *messageArgument)
|
||||
{
|
||||
Q_UNUSED(service)
|
||||
|
||||
auto listener = static_cast<PowerNotificationsListener *>(refParam);
|
||||
|
||||
logger.debug() << "Power callback received, messageType:" << messageType;
|
||||
switch (messageType) {
|
||||
case kIOMessageCanSystemSleep:
|
||||
/* Idle sleep is about to kick in. This message will not be sent for forced sleep.
|
||||
* Applications have a chance to prevent sleep by calling IOCancelPowerChange.
|
||||
* Most applications should not prevent idle sleep. Power Management waits up to
|
||||
* 30 seconds for you to either allow or deny idle sleep. If you don’t acknowledge
|
||||
* this power change by calling either IOAllowPowerChange or IOCancelPowerChange,
|
||||
* the system will wait 30 seconds then go to sleep.
|
||||
*/
|
||||
|
||||
logger.debug() << "System power message: can system sleep?";
|
||||
|
||||
// Uncomment to cancel idle sleep
|
||||
// IOCancelPowerChange(thiz->rootPowerDomain, reinterpret_cast<long>(messageArgument));
|
||||
|
||||
// Allow idle sleep
|
||||
IOAllowPowerChange(listener->rootPowerDomain, reinterpret_cast<long>(messageArgument));
|
||||
break;
|
||||
|
||||
case kIOMessageSystemWillNotSleep:
|
||||
/* Announces that the system has retracted a previous attempt to sleep; it
|
||||
* follows `kIOMessageCanSystemSleep`.
|
||||
*/
|
||||
logger.debug() << "System power message: system will NOT sleep.";
|
||||
break;
|
||||
|
||||
case kIOMessageSystemWillSleep:
|
||||
/* The system WILL go to sleep. If you do not call IOAllowPowerChange or
|
||||
* IOCancelPowerChange to acknowledge this message, sleep will be delayed by
|
||||
* 30 seconds.
|
||||
*
|
||||
* NOTE: If you call IOCancelPowerChange to deny sleep it returns kIOReturnSuccess,
|
||||
* however the system WILL still go to sleep.
|
||||
*/
|
||||
|
||||
logger.debug() << "System power message: system WILL sleep";
|
||||
IOAllowPowerChange(listener->rootPowerDomain, reinterpret_cast<long>(messageArgument));
|
||||
break;
|
||||
|
||||
case kIOMessageSystemWillPowerOn:
|
||||
/* Announces that the system is beginning to power the device tree; most devices
|
||||
* are still unavailable at this point.
|
||||
*/
|
||||
/* From the documentation:
|
||||
*
|
||||
* - kIOMessageSystemWillPowerOn is delivered at early wakeup time, before most hardware
|
||||
* has been powered on. Be aware that any attempts to access disk, network, the display,
|
||||
* etc. may result in errors or blocking your process until those resources become
|
||||
* available.
|
||||
*
|
||||
* So we do NOT log this event.
|
||||
*/
|
||||
break;
|
||||
|
||||
case kIOMessageSystemHasPoweredOn:
|
||||
/* Announces that the system and its devices have woken up. */
|
||||
logger.debug() << "System has powered on - emitting sleepMode signal from dedicated CFRunLoop thread";
|
||||
if (listener->m_watcher) {
|
||||
// Use QMetaObject::invokeMethod for thread-safe signal emission
|
||||
QMetaObject::invokeMethod(listener->m_watcher, "sleepMode", Qt::QueuedConnection);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.debug() << "System power message: other event: " << messageType;
|
||||
/* Not a system sleep and wake notification. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : IOSNetworkWatcher(parent), m_powerlistener(this) {
|
||||
MZ_COUNT_CTOR(MacOSNetworkWatcher);
|
||||
}
|
||||
|
||||
MacOSNetworkWatcher::~MacOSNetworkWatcher() {
|
||||
MZ_COUNT_DTOR(MacOSNetworkWatcher);
|
||||
|
||||
// Stop the dedicated power monitoring thread
|
||||
if (g_powerListener) {
|
||||
logger.debug() << "Stopping dedicated power monitoring thread";
|
||||
g_shouldStopPowerThread = true;
|
||||
|
||||
if (g_powerRunLoop) {
|
||||
CFRunLoopStop(g_powerRunLoop);
|
||||
}
|
||||
|
||||
// Wait for thread to finish
|
||||
pthread_join(g_powerThread, nullptr);
|
||||
g_powerListener = nullptr;
|
||||
}
|
||||
|
||||
if (m_delegate) {
|
||||
CWWiFiClient* client = CWWiFiClient.sharedWiFiClient;
|
||||
if (!client) {
|
||||
@@ -66,6 +228,20 @@ void MacOSNetworkWatcher::start() {
|
||||
logger.debug() << "Delegate already registered";
|
||||
return;
|
||||
}
|
||||
|
||||
// Start dedicated power monitoring thread with CFRunLoop
|
||||
if (!g_powerListener) {
|
||||
g_powerListener = &m_powerlistener;
|
||||
g_shouldStopPowerThread = false;
|
||||
|
||||
int result = pthread_create(&g_powerThread, nullptr, powerMonitoringThread, &m_powerlistener);
|
||||
if (result != 0) {
|
||||
logger.error() << "Failed to create power monitoring thread:" << result;
|
||||
g_powerListener = nullptr;
|
||||
} else {
|
||||
logger.debug() << "Power monitoring enabled";
|
||||
}
|
||||
}
|
||||
|
||||
CWWiFiClient* client = CWWiFiClient.sharedWiFiClient;
|
||||
if (!client) {
|
||||
@@ -77,6 +253,8 @@ void MacOSNetworkWatcher::start() {
|
||||
m_delegate = [[MacOSNetworkWatcherDelegate alloc] initWithObject:this];
|
||||
[client setDelegate:static_cast<MacOSNetworkWatcherDelegate*>(m_delegate)];
|
||||
[client startMonitoringEventWithType:CWEventTypeBSSIDDidChange error:nullptr];
|
||||
|
||||
logger.debug() << "MacOSNetworkWatcher started successfully";
|
||||
}
|
||||
|
||||
void MacOSNetworkWatcher::checkInterface() {
|
||||
@@ -87,42 +265,70 @@ void MacOSNetworkWatcher::checkInterface() {
|
||||
return;
|
||||
}
|
||||
|
||||
CWWiFiClient* client = CWWiFiClient.sharedWiFiClient;
|
||||
if (!client) {
|
||||
logger.debug() << "Unable to retrieve the CWWiFiClient shared instance";
|
||||
// Use wdutil to get reliable WiFi information
|
||||
QProcess process;
|
||||
process.start("wdutil", QStringList() << "info");
|
||||
process.waitForFinished(5000);
|
||||
|
||||
QString output = process.readAllStandardOutput();
|
||||
QString errorOutput = process.readAllStandardError();
|
||||
|
||||
logger.debug() << "wdutil exit code:" << process.exitCode();
|
||||
|
||||
if (process.exitCode() != 0) {
|
||||
logger.debug() << "wdutil failed with exit code:" << process.exitCode();
|
||||
return;
|
||||
}
|
||||
|
||||
CWInterface* interface = [client interface];
|
||||
if (!interface) {
|
||||
logger.debug() << "No default wifi interface";
|
||||
return;
|
||||
|
||||
// Parse wdutil output to find WiFi connection info
|
||||
QStringList lines = output.split('\n');
|
||||
QString ssid, interfaceName, security;
|
||||
bool wifiSectionFound = false;
|
||||
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
QString trimmedLine = lines[i].trimmed();
|
||||
|
||||
if (trimmedLine == "WIFI") {
|
||||
wifiSectionFound = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wifiSectionFound) {
|
||||
// Stop parsing when we reach next section header (all caps after separator line)
|
||||
if (trimmedLine.startsWith("————————")) {
|
||||
if (i + 1 < lines.size()) {
|
||||
QString nextLine = lines[i + 1].trimmed();
|
||||
if (!nextLine.isEmpty() && nextLine.length() > 2 && nextLine.toUpper() == nextLine && nextLine != "WIFI") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue; // Skip separator lines
|
||||
}
|
||||
|
||||
if (trimmedLine.startsWith("Interface Name")) {
|
||||
QStringList parts = trimmedLine.split(":");
|
||||
if (parts.size() >= 2) {
|
||||
interfaceName = parts[1].trimmed();
|
||||
}
|
||||
} else if (trimmedLine.startsWith("SSID")) {
|
||||
QStringList parts = trimmedLine.split(":");
|
||||
if (parts.size() >= 2) {
|
||||
ssid = parts[1].trimmed();
|
||||
}
|
||||
} else if (trimmedLine.startsWith("Security")) {
|
||||
QStringList parts = trimmedLine.split(":");
|
||||
if (parts.size() >= 2) {
|
||||
security = parts[1].trimmed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (![interface powerOn]) {
|
||||
logger.debug() << "The interface is off";
|
||||
return;
|
||||
|
||||
if (!ssid.isEmpty() && !interfaceName.isEmpty()) {
|
||||
logger.debug() << "Found active WiFi connection on" << interfaceName
|
||||
<< "SSID:" << ssid << "Security:" << security;
|
||||
} else {
|
||||
logger.debug() << "No active WiFi connection found";
|
||||
}
|
||||
|
||||
NSString* ssidNS = [interface ssid];
|
||||
if (!ssidNS) {
|
||||
logger.debug() << "WiFi is not in used";
|
||||
return;
|
||||
}
|
||||
|
||||
QString ssid = QString::fromNSString(ssidNS);
|
||||
if (ssid.isEmpty()) {
|
||||
logger.debug() << "WiFi doesn't have a valid SSID";
|
||||
return;
|
||||
}
|
||||
|
||||
CWSecurity security = [interface security];
|
||||
if (security == kCWSecurityNone || security == kCWSecurityWEP) {
|
||||
logger.debug() << "Unsecured network found!";
|
||||
emit unsecuredNetwork(ssid, ssid);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug() << "Secure WiFi interface";
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "logger.h"
|
||||
#include "platforms/windows/daemon/windowsfirewall.h"
|
||||
#include "platforms/windows/daemon/windowssplittunnel.h"
|
||||
#include "platforms/windows/windowscommons.h"
|
||||
#include "windowsfirewall.h"
|
||||
|
||||
#include "core/networkUtilities.h"
|
||||
|
||||
@@ -32,9 +32,28 @@ WindowsNetworkWatcher::~WindowsNetworkWatcher() {
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT WindowsNetworkWatcher::PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
auto obj = reinterpret_cast<WindowsNetworkWatcher*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||
if (!obj){
|
||||
logger.debug() << "obj not casted";
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
switch (uMsg) {
|
||||
case WM_POWERBROADCAST:
|
||||
if (wParam == PBT_APMRESUMESUSPEND) {
|
||||
emit obj->sleepMode();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WindowsNetworkWatcher::initialize() {
|
||||
logger.debug() << "initialize";
|
||||
|
||||
|
||||
DWORD negotiatedVersion;
|
||||
if (WlanOpenHandle(2, nullptr, &negotiatedVersion, &m_wlanHandle) !=
|
||||
ERROR_SUCCESS) {
|
||||
@@ -51,6 +70,25 @@ void WindowsNetworkWatcher::initialize() {
|
||||
return;
|
||||
}
|
||||
|
||||
const wchar_t* className = L"PowerMonitorClass";
|
||||
WNDCLASS wc = { 0 };
|
||||
wc.lpfnWndProc = &WindowsNetworkWatcher::PowerWndProcCallback;
|
||||
wc.hInstance = GetModuleHandle(NULL);
|
||||
wc.lpszClassName = className;
|
||||
wc.cbWndExtra = sizeof(WindowsNetworkWatcher*);
|
||||
|
||||
if (!RegisterClass(&wc)) {
|
||||
logger.debug() << "Failed to register window class in createPowerMonitorWindow.";
|
||||
return;
|
||||
}
|
||||
|
||||
HWND hwnd = CreateWindowEx(0, className, L"Power Monitor", 0, 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), static_cast<LPVOID>(this));
|
||||
if (!hwnd) {
|
||||
logger.debug() << "Failed to create window in createPowerMonitorWindow.";
|
||||
return;
|
||||
}
|
||||
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
|
||||
|
||||
logger.debug() << "callback registered";
|
||||
}
|
||||
|
||||
@@ -137,4 +175,4 @@ void WindowsNetworkWatcher::processWlan(PWLAN_NOTIFICATION_DATA data) {
|
||||
logger.debug() << "Unsecure network:" << logger.sensitive(ssid)
|
||||
<< "id:" << logger.sensitive(bssid);
|
||||
emit unsecuredNetwork(ssid, bssid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class WindowsNetworkWatcher final : public NetworkWatcherImpl {
|
||||
|
||||
private:
|
||||
static void wlanCallback(PWLAN_NOTIFICATION_DATA data, PVOID context);
|
||||
static LRESULT PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
void processWlan(PWLAN_NOTIFICATION_DATA data);
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ void WindowsPingSender::pingEventReady() {
|
||||
return;
|
||||
}
|
||||
QString errmsg = WindowsUtils::getErrorMessage();
|
||||
emit criticalPingError();
|
||||
logger.error() << "No ping reply. Code: " << error
|
||||
<< " Message: " << errmsg;
|
||||
return;
|
||||
|
||||
@@ -30,7 +30,6 @@ Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent)
|
||||
Ikev2Protocol::~Ikev2Protocol()
|
||||
{
|
||||
qDebug() << "IpsecProtocol::~IpsecProtocol()";
|
||||
disconnect_vpn();
|
||||
Ikev2Protocol::stop();
|
||||
}
|
||||
|
||||
@@ -38,7 +37,7 @@ void Ikev2Protocol::stop()
|
||||
{
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
{
|
||||
if (! disconnect_vpn() ){
|
||||
if (!disconnect_vpn()){
|
||||
qDebug()<<"We don't disconnect";
|
||||
setConnectionState(Vpn::ConnectionState::Error);
|
||||
}
|
||||
@@ -311,7 +310,9 @@ bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
bool Ikev2Protocol::disconnect_vpn(){
|
||||
if ( hRasConn != nullptr ){
|
||||
if ( RasHangUp(hRasConn) != ERROR_SUCCESS)
|
||||
auto ret = RasHangUp(hRasConn);
|
||||
qDebug() << "RasHangUp " << ret;
|
||||
if (ret != ERROR_SUCCESS)
|
||||
return false;
|
||||
}
|
||||
QThread::msleep(3000);
|
||||
|
||||
@@ -103,6 +103,11 @@ QString VpnProtocol::vpnGateway() const
|
||||
return m_vpnGateway;
|
||||
}
|
||||
|
||||
QString VpnProtocol::vpnLocalAddress() const
|
||||
{
|
||||
return m_vpnLocalAddress;
|
||||
}
|
||||
|
||||
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
|
||||
{
|
||||
switch (container) {
|
||||
|
||||
@@ -63,6 +63,7 @@ public:
|
||||
|
||||
QString routeGateway() const;
|
||||
QString vpnGateway() const;
|
||||
QString vpnLocalAddress() const;
|
||||
|
||||
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
|
||||
|
||||
|
||||
@@ -17,6 +17,13 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
||||
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
|
||||
emit connectionStateChanged(Vpn::ConnectionState::Connected);
|
||||
});
|
||||
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
|
||||
[this](const QString& serverIpv4Gateway,
|
||||
const QString& deviceIpv4Address, uint64_t txBytes,
|
||||
uint64_t rxBytes) {
|
||||
m_vpnLocalAddress = deviceIpv4Address;
|
||||
});
|
||||
|
||||
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
||||
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); });
|
||||
m_impl->initialize(nullptr, nullptr);
|
||||
|
||||
@@ -229,7 +229,6 @@
|
||||
<file>ui/qml/Pages2/PageShare.qml</file>
|
||||
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
|
||||
<file>ui/qml/Pages2/PageShareConnection.qml</file>
|
||||
<file>ui/qml/Pages2/PageTransferConfigViaQR.qml</file>
|
||||
<file>ui/qml/Pages2/PageStart.qml</file>
|
||||
<file>ui/qml/Components/RenameServerDrawer.qml</file>
|
||||
<file>ui/qml/Controls2/ListViewType.qml</file>
|
||||
@@ -237,11 +236,7 @@
|
||||
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiDevicesLimit.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiAddDeviceScan.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiAddDeviceConfirm.qml</file>
|
||||
<file>images/controls/monitor.svg</file>
|
||||
<file>images/controls/monitor-with-phone.svg</file>
|
||||
<file>ui/qml/Components/ApiPremV1MigrationDrawer.qml</file>
|
||||
<file>ui/qml/Components/ApiPremV1SubListDrawer.qml</file>
|
||||
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
|
||||
|
||||
Executable
+519
@@ -0,0 +1,519 @@
|
||||
#!/bin/sh
|
||||
|
||||
LOG_DATE=$(date -u +'%Y%m%d-%H%M%S')
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
LOG_FILE="${SCRIPT_DIR}/server-diagnostics-${LOG_DATE}.log"
|
||||
|
||||
# Logging function (sh compatible)
|
||||
log_and_display() {
|
||||
if [ "$1" = "-n" ]; then
|
||||
shift
|
||||
printf "%s" "$*" | tee -a "$LOG_FILE"
|
||||
else
|
||||
echo "$1" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Redirect stderr to stdout for logging
|
||||
exec 2>&1
|
||||
|
||||
header() {
|
||||
log_and_display ""
|
||||
log_and_display "=== $1 ==="
|
||||
}
|
||||
|
||||
# Pause for cancellation
|
||||
log_and_display ""
|
||||
log_and_display "VPN Server Diagnostics will start in 9s. Press Ctrl+C to cancel."
|
||||
sleep 9
|
||||
|
||||
log_and_display ""
|
||||
header "STARTING VPN SERVER DIAGNOSTICS"
|
||||
log_and_display ""
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 1. Basic system information
|
||||
# ------------------------------------------------------------------------------
|
||||
header "System Information"
|
||||
|
||||
# Uptime
|
||||
UPTIME_STR=$(awk '{printf "%d:%02d:%02d", int($1/3600), int(($1%3600)/60), int($1%60)}' /proc/uptime 2>/dev/null || echo "unknown")
|
||||
log_and_display "Uptime (H:M:S): $UPTIME_STR"
|
||||
|
||||
# Date/time UTC
|
||||
DATE_UTC=$(date -u +'%d %b %Y|%T' 2>/dev/null || echo "unknown")
|
||||
log_and_display "Date|Time (UTC): $DATE_UTC"
|
||||
|
||||
# Init system (PID 1)
|
||||
INIT_NAME=$(cat /proc/1/status 2>/dev/null | head -1 | awk '{print $2}' 2>/dev/null || echo "unknown")
|
||||
log_and_display "Init system (PID 1): $INIT_NAME"
|
||||
|
||||
# Locale
|
||||
if echo "$LANG" | grep -E '^(en_US.UTF-8|C.UTF-8|C)$' >/dev/null 2>&1; then
|
||||
log_and_display "Locale: $LANG"
|
||||
else
|
||||
log_and_display "Locale: $LANG (not en_US.UTF-8, C.UTF-8 or C)"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 2. Package manager detection
|
||||
# ------------------------------------------------------------------------------
|
||||
header "Package Manager Information"
|
||||
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: APT"
|
||||
PM="apt-get"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker.io"
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: DNF"
|
||||
PM="dnf"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker"
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: YUM"
|
||||
PM="yum"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker"
|
||||
elif command -v zypper >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: ZYPPER"
|
||||
PM="zypper"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker"
|
||||
elif command -v pacman >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: PACMAN"
|
||||
PM="pacman"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker"
|
||||
elif command -v opkg >/dev/null 2>&1; then
|
||||
log_and_display "Package Manager: OPKG - Not supported on this platform"
|
||||
PM="opkg"
|
||||
PM_VER_OPT="--version"
|
||||
DOCKER_PKG="docker"
|
||||
else
|
||||
log_and_display "Package Manager: Unknown"
|
||||
# fallback
|
||||
PM="uname"
|
||||
PM_VER_OPT="-a"
|
||||
DOCKER_PKG="docker"
|
||||
fi
|
||||
|
||||
# Check package versions
|
||||
log_and_display ""
|
||||
log_and_display "Package versions:"
|
||||
|
||||
# Check sudo
|
||||
if [ "$PM" = "apt-get" ]; then
|
||||
sudo_version=$(dpkg -s "sudo" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
|
||||
sudo_version=$(rpm -q "sudo" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "pacman" ]; then
|
||||
sudo_version=$(pacman -Q "sudo" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "opkg" ]; then
|
||||
sudo_version=$(opkg info "sudo" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
else
|
||||
sudo_version="unknown"
|
||||
fi
|
||||
log_and_display " sudo: $sudo_version"
|
||||
|
||||
# Check Docker package
|
||||
if [ "$PM" = "apt-get" ]; then
|
||||
docker_pkg_version=$(dpkg -s "$DOCKER_PKG" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
|
||||
docker_pkg_version=$(rpm -q "$DOCKER_PKG" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "pacman" ]; then
|
||||
docker_pkg_version=$(pacman -Q "$DOCKER_PKG" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "opkg" ]; then
|
||||
docker_pkg_version=$(opkg info "$DOCKER_PKG" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
else
|
||||
docker_pkg_version="unknown"
|
||||
fi
|
||||
log_and_display " $DOCKER_PKG: $docker_pkg_version"
|
||||
|
||||
# Check lsof
|
||||
if [ "$PM" = "apt-get" ]; then
|
||||
lsof_version=$(dpkg -s "lsof" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
|
||||
lsof_version=$(rpm -q "lsof" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "pacman" ]; then
|
||||
lsof_version=$(pacman -Q "lsof" 2>/dev/null || echo "not installed")
|
||||
elif [ "$PM" = "opkg" ]; then
|
||||
lsof_version=$(opkg info "lsof" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
|
||||
else
|
||||
lsof_version="unknown"
|
||||
fi
|
||||
log_and_display " lsof: $lsof_version"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 3. Additional system information (hostnamectl / /proc/version)
|
||||
# ------------------------------------------------------------------------------
|
||||
header "OS / Kernel Information"
|
||||
|
||||
if command -v hostnamectl >/dev/null 2>&1; then
|
||||
hostnamectl 2>/dev/null | grep -E 'Operating System:|Virtualization:|Kernel:|Architecture:' | sed 's/^[ \t]*//;s/:/: /' | while read line; do
|
||||
log_and_display " $line"
|
||||
done
|
||||
else
|
||||
log_and_display "Operating System: $(cat /proc/version 2>/dev/null || echo 'unknown')"
|
||||
fi
|
||||
|
||||
# CPU threads
|
||||
CPU_THREADS=$(nproc 2>/dev/null || grep -c "^processor" /proc/cpuinfo 2>/dev/null || echo "unknown")
|
||||
log_and_display " CPU threads: $CPU_THREADS"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 4. Memory (RAM) check
|
||||
# ------------------------------------------------------------------------------
|
||||
header "Memory Information"
|
||||
|
||||
if command -v free >/dev/null 2>&1; then
|
||||
# Remove extra spaces in header
|
||||
free -h 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
|
||||
elif command -v vmstat >/dev/null 2>&1; then
|
||||
vmstat -S M -s 2>/dev/null | grep -iE 'total memory|total swap' | sed 's/ *//' | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
|
||||
else
|
||||
grep -iE 'MemTotal|SwapTotal' /proc/meminfo 2>/dev/null | sed 's/ \+/ /' | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
|
||||
fi
|
||||
|
||||
if command -v free >/dev/null 2>&1; then
|
||||
log_and_display ""
|
||||
log_and_display "Detailed Memory Info:"
|
||||
free -h 2>/dev/null | awk 'NR==2{printf " Used: %s / %s (%.1f%%)\n", $3, $2, $3/$2*100}' 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error calculating memory usage"
|
||||
free -h 2>/dev/null | awk 'NR==3{printf " Swap: %s / %s (%.1f%%)\n", $3, $2, $2>0 ? $3/$2*100 : 0}' 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error calculating swap usage"
|
||||
fi
|
||||
|
||||
# Disk usage
|
||||
header "Disk Usage"
|
||||
df -h 2>/dev/null | awk '
|
||||
BEGIN {print " Filesystem Size Used Avail Use% Mounted"}
|
||||
NR>1 {printf " %-10s %5s %5s %5s %4s %s\n", $1, $2, $3, $4, $5, $6}' | tee -a "$LOG_FILE" || log_and_display " Error getting disk usage"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 5. Current user and sudo check
|
||||
# ------------------------------------------------------------------------------
|
||||
header "User Check"
|
||||
|
||||
CUR_USER=$(whoami 2>/dev/null || echo ~ | sed 's/.*\///')
|
||||
USER_GROUP=$(groups "$CUR_USER" 2>/dev/null || echo "")
|
||||
USER_GOOD=0
|
||||
|
||||
log_and_display -n "Current user: $CUR_USER => "
|
||||
|
||||
if [ "$CUR_USER" = "root" ]; then
|
||||
log_and_display "passed.. (is root)"
|
||||
USER_GOOD="r" # root
|
||||
else
|
||||
if echo "$USER_GROUP" | grep -qE '(^|[[:space:]])sudo($|[[:space:]])'; then
|
||||
log_and_display "passed.. (in sudo group)"
|
||||
USER_GOOD=1
|
||||
elif echo "$USER_GROUP" | grep -qE '(^|[[:space:]])wheel($|[[:space:]])'; then
|
||||
log_and_display "passed.. (in wheel group)"
|
||||
USER_GOOD=1
|
||||
elif echo "$USER_GROUP" | grep -qE '(^|[[:space:]])docker($|[[:space:]])'; then
|
||||
log_and_display "failed.. (only in docker group)"
|
||||
USER_GOOD="d"
|
||||
else
|
||||
log_and_display "failed.. (not a member of the sudo or wheel groups)"
|
||||
USER_GOOD=0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if password is required for sudo
|
||||
if [ "$USER_GOOD" = "0" ] || [ "$USER_GOOD" = "d" ]; then
|
||||
log_and_display -n "Passwd request: "
|
||||
log_and_display "check skipped (not sudoer)"
|
||||
else
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
# Try sudo without password - more thorough check
|
||||
PASSWD_REQUEST=$(sudo -K 2>&1 && sudo -nu $CUR_USER $PM $PM_VER_OPT 2>&1 >/dev/null && sudo -n $PM $PM_VER_OPT 2>&1 >/dev/null)
|
||||
if [ -n "$PASSWD_REQUEST" ]; then
|
||||
USER_GOOD=0
|
||||
log_and_display -n "Passwd request: "
|
||||
log_and_display "failed.. ($PASSWD_REQUEST)" \
|
||||
| sed "s/$CUR_USER/User/g;s/$(hostname 2>/dev/null || echo 'Server')/Server/g;s/ user / /g"
|
||||
else
|
||||
log_and_display -n "Passwd request: "
|
||||
log_and_display "passed.. (not required)"
|
||||
fi
|
||||
else
|
||||
if [ "$USER_GOOD" = "r" ]; then
|
||||
log_and_display -n "Passwd request: "
|
||||
log_and_display "check skipped (sudo not installed, but root user)"
|
||||
else
|
||||
log_and_display "Warning! The sudo package must be pre-installed!"
|
||||
USER_GOOD=0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Home directory check
|
||||
log_and_display -n "Home dir: "
|
||||
if cd ~ 2>/dev/null; then
|
||||
log_and_display "passed.. (accessible)"
|
||||
else
|
||||
log_and_display "failed.. (not accessible)"
|
||||
fi
|
||||
log_and_display "Default shell: $SHELL"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 6. Important components check (sudo, lsof, fuser, apparmor)
|
||||
# ------------------------------------------------------------------------------
|
||||
header "Component Checks"
|
||||
|
||||
log_and_display -n " sudo: "
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
log_and_display "passed.. (installed)"
|
||||
else
|
||||
log_and_display "not installed"
|
||||
fi
|
||||
|
||||
log_and_display -n " lsof: "
|
||||
if command -v lsof >/dev/null 2>&1; then
|
||||
log_and_display "passed.. (installed)"
|
||||
else
|
||||
log_and_display "not installed"
|
||||
fi
|
||||
|
||||
log_and_display -n " fuser: "
|
||||
if command -v fuser >/dev/null 2>&1; then
|
||||
log_and_display "passed.. (installed)"
|
||||
else
|
||||
log_and_display "psmisc not installed"
|
||||
fi
|
||||
|
||||
log_and_display -n "apparmor: "
|
||||
AA_ENABLED=$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null || echo "N")
|
||||
if [ "$AA_ENABLED" = "Y" ]; then
|
||||
if command -v apparmor_parser >/dev/null 2>&1; then
|
||||
log_and_display "passed.. (used)"
|
||||
else
|
||||
log_and_display "failed.. (installation required)"
|
||||
fi
|
||||
else
|
||||
if command -v apparmor_parser >/dev/null 2>&1; then
|
||||
log_and_display "passed.. (not used)"
|
||||
else
|
||||
log_and_display "passed.. (not required)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 7. SELinux check
|
||||
# ------------------------------------------------------------------------------
|
||||
header "SELinux Check"
|
||||
|
||||
if command -v getenforce >/dev/null 2>&1; then
|
||||
SELINUX_STATUS=$(getenforce 2>/dev/null || echo "unknown")
|
||||
if [ "$SELINUX_STATUS" = "Enforcing" ]; then
|
||||
log_and_display "SELinux status: $SELINUX_STATUS (strict mode)"
|
||||
elif [ "$SELINUX_STATUS" = "Permissive" ]; then
|
||||
log_and_display "SELinux status: $SELINUX_STATUS (permissive mode)"
|
||||
else
|
||||
log_and_display "SELinux status: $SELINUX_STATUS (disabled)"
|
||||
fi
|
||||
else
|
||||
log_and_display "SELinux: not found (or not applicable)"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 8. Docker + Docker/Podman service check
|
||||
# ------------------------------------------------------------------------------
|
||||
header "Docker / Podman Status"
|
||||
CHECK_CONTAINERS=0
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
log_and_display "Docker: $DOCKER_PKG not installed"
|
||||
else
|
||||
# If user is in sudoers, use sudo without password
|
||||
if [ "$USER_GOOD" = "1" ]; then
|
||||
SUD="sudo -n"
|
||||
elif [ "$USER_GOOD" = "r" ]; then
|
||||
SUD="" # root
|
||||
else
|
||||
SUD=""
|
||||
fi
|
||||
|
||||
DOCKER_VERSION=$($SUD docker -v 2>/dev/null || echo 'docker -v error')
|
||||
log_and_display "Installed: $DOCKER_VERSION"
|
||||
|
||||
# Check for podman
|
||||
if echo "$DOCKER_VERSION" | grep -qi "podman"; then
|
||||
log_and_display " WARNING: Podman detected - not supported at the moment!"
|
||||
log_and_display " Podman (podman-docker) is not supported and is installed by mistake"
|
||||
docker_service="podman.socket"
|
||||
else
|
||||
docker_service="docker.service"
|
||||
fi
|
||||
log_and_display " service: $docker_service"
|
||||
|
||||
# Check status
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
docker_status=$(systemctl is-active "$docker_service" 2>/dev/null || echo "unknown")
|
||||
docker_loading=$(systemctl is-enabled "$docker_service" 2>/dev/null || echo "unknown")
|
||||
else
|
||||
docker_status="unknown (systemctl not found)"
|
||||
docker_loading="unknown"
|
||||
fi
|
||||
|
||||
if [ "$docker_status" = "active" ]; then
|
||||
log_and_display " status: passed.. ($docker_status)"
|
||||
CHECK_CONTAINERS=1
|
||||
else
|
||||
log_and_display " status: incorrect.. ($docker_status)"
|
||||
CHECK_CONTAINERS=0
|
||||
fi
|
||||
|
||||
if [ "$docker_loading" = "enabled" ]; then
|
||||
log_and_display " loading: good (startup $docker_loading)"
|
||||
else
|
||||
log_and_display " loading: bad (startup $docker_loading)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 9. Docker pull test + container check with improved Docker Hub verification
|
||||
# ------------------------------------------------------------------------------
|
||||
header "Docker Hub: pull hello-world test"
|
||||
|
||||
if [ "$CHECK_CONTAINERS" = "1" ] && [ "$USER_GOOD" != "0" ]; then
|
||||
# First check Docker Hub availability
|
||||
log_and_display "Checking Docker Hub connectivity..."
|
||||
|
||||
# Try to execute docker pull with timeout
|
||||
if timeout 30 $SUD docker pull docker.io/library/hello-world >/dev/null 2>&1; then
|
||||
log_and_display "Docker Hub: available"
|
||||
|
||||
# Start container for testing
|
||||
if $SUD docker run --rm docker.io/library/hello-world >/dev/null 2>&1; then
|
||||
log_and_display "Hello-world container: successfully started and completed"
|
||||
else
|
||||
log_and_display "Hello-world container: startup error"
|
||||
fi
|
||||
else
|
||||
log_and_display "Docker Hub: unavailable or blocked (possibly exceeded download limit)"
|
||||
log_and_display "Docker Hub has download limits, try again later"
|
||||
fi
|
||||
|
||||
log_and_display ""
|
||||
total_cont=$($SUD docker ps -aq 2>/dev/null | wc -l || echo "0")
|
||||
active_cont=$($SUD docker ps -q 2>/dev/null | wc -l || echo "0")
|
||||
amnezia_cont=$($SUD docker ps -a 2>/dev/null | grep -c amnezia || echo "0")
|
||||
|
||||
log_and_display "Containers check: Total $total_cont / Active $active_cont / Amnezia $amnezia_cont"
|
||||
$SUD docker ps -a --format "{{.Names}} ({{.Image}}) ({{.Status}}) ({{.Ports}})" 2>/dev/null | grep amnezia || true
|
||||
|
||||
# Peers check
|
||||
if $SUD docker ps 2>/dev/null | grep -qE '\<(amnezia-awg|amnezia-wireguard)\>'; then
|
||||
log_and_display ""
|
||||
log_and_display "Peers check (beta):"
|
||||
if $SUD docker ps 2>/dev/null | grep -q amnezia-awg; then
|
||||
AMNEZIA_WG_CONTAINER=$($SUD docker ps 2>/dev/null | grep amnezia-awg | awk '{print $1}' | head -1)
|
||||
if [ -n "$AMNEZIA_WG_CONTAINER" ]; then
|
||||
WG_PEERS=$($SUD docker exec -it "$AMNEZIA_WG_CONTAINER" wg show 2>/dev/null | grep -c 'peer' || echo "0")
|
||||
log_and_display "AmneziaWG peers: $WG_PEERS"
|
||||
fi
|
||||
fi
|
||||
if $SUD docker ps 2>/dev/null | grep -q amnezia-wireguard; then
|
||||
WIREGUARD_CONTAINER=$($SUD docker ps 2>/dev/null | grep amnezia-wireguard | awk '{print $1}' | head -1)
|
||||
if [ -n "$WIREGUARD_CONTAINER" ]; then
|
||||
WG_PEERS=$($SUD docker exec -it "$WIREGUARD_CONTAINER" wg show 2>/dev/null | grep -c 'peer' || echo "0")
|
||||
log_and_display "WireGuard peers: $WG_PEERS"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_and_display "skipped.."
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 10. Additional improvements
|
||||
# ------------------------------------------------------------------------------
|
||||
#
|
||||
# 10.1. CPU and memory load check (Load average, top processes)
|
||||
#
|
||||
header "CPU & Memory usage (top)"
|
||||
|
||||
# Load average (last 1,5,15 minutes)
|
||||
LOAD_AVG=$(uptime 2>/dev/null | awk -F'load average:' '{print $2}' || echo "unknown")
|
||||
log_and_display "Load average: $LOAD_AVG"
|
||||
|
||||
log_and_display ""
|
||||
log_and_display "Top 5 processes by CPU:"
|
||||
ps aux 2>/dev/null | sort -k3 -nr | head -n 6 | awk '{printf "%s %s %s %s %s\n", $1,$2,$3"%",$4"%",$11}' | column -t 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting CPU processes"
|
||||
|
||||
log_and_display ""
|
||||
log_and_display "Top 5 processes by MEM:"
|
||||
ps aux 2>/dev/null | sort -k4 -nr | head -n 6 | awk '{printf "%s %s %s %s %s\n", $1,$2,$3"%",$4"%",$11}' | column -t 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting MEM processes"
|
||||
|
||||
# 10.2. System logs check (latest critical messages)
|
||||
header "Last 10 critical/error messages (journalctl)"
|
||||
|
||||
if command -v journalctl >/dev/null 2>&1; then
|
||||
journalctl -p 3 -n 10 --no-pager 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting system logs"
|
||||
else
|
||||
log_and_display "journalctl not found (non-systemd system?)"
|
||||
fi
|
||||
|
||||
# 10.3. System package versions check (examples)
|
||||
|
||||
# Open ports check
|
||||
header "Network Ports Check"
|
||||
if command -v netstat >/dev/null 2>&1; then
|
||||
log_and_display "Listening ports:"
|
||||
netstat -tlnp 2>/dev/null | grep LISTEN | head -10 | while read line; do
|
||||
log_and_display " $line"
|
||||
done
|
||||
elif command -v ss >/dev/null 2>&1; then
|
||||
log_and_display "Listening ports:"
|
||||
ss -tlnp 2>/dev/null | head -10 | while read line; do
|
||||
log_and_display " $line"
|
||||
done
|
||||
else
|
||||
log_and_display "netstat/ss not found"
|
||||
fi
|
||||
|
||||
# SSH check
|
||||
header "SSH Service Check"
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
ssh_status=$(systemctl is-active ssh 2>/dev/null || systemctl is-active sshd 2>/dev/null || echo "not found")
|
||||
if [ "$ssh_status" = "active" ]; then
|
||||
log_and_display "SSH service: $ssh_status"
|
||||
else
|
||||
log_and_display "SSH service: $ssh_status"
|
||||
fi
|
||||
else
|
||||
log_and_display "systemctl not found"
|
||||
fi
|
||||
|
||||
# Time check
|
||||
header "Time Synchronization"
|
||||
if command -v timedatectl >/dev/null 2>&1; then
|
||||
timedatectl status 2>/dev/null | grep -E "System clock|NTP service" | while read line; do
|
||||
log_and_display " $line"
|
||||
done
|
||||
else
|
||||
log_and_display " System time: $(date 2>/dev/null || echo 'unknown')"
|
||||
fi
|
||||
|
||||
# Kernel check
|
||||
header "Kernel Information"
|
||||
log_and_display "Kernel version: $(uname -r 2>/dev/null || echo 'unknown')"
|
||||
log_and_display "Kernel architecture: $(uname -m 2>/dev/null || echo 'unknown')"
|
||||
if [ -f /proc/cmdline ]; then
|
||||
log_and_display "Kernel parameters:"
|
||||
cat /proc/cmdline 2>/dev/null | tr ' ' '\n' | head -5 | while read param; do
|
||||
log_and_display " $param"
|
||||
done
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Completion
|
||||
# ------------------------------------------------------------------------------
|
||||
log_and_display ""
|
||||
header "FINISH"
|
||||
log_and_display ""
|
||||
log_and_display "Diagnostics completed. Log saved to: $LOG_FILE"
|
||||
log_and_display ""
|
||||
|
||||
# Variable cleanup
|
||||
pm="" && opt="" && docker_pkg="" && CUR_USER="" && USER_GOOD="" && USER_GROUP="" && PASSWD_REQUEST="" && CHECK_CONTAINERS="" && SUD="" && docker_service="" && docker_status="" && docker_loading=""
|
||||
+1
-1
@@ -174,7 +174,7 @@ public:
|
||||
|
||||
QLocale getAppLanguage()
|
||||
{
|
||||
QString localeStr = m_settings.value("Conf/appLanguage").toString();
|
||||
QString localeStr = m_settings.value("Conf/appLanguage", QLocale::system().name()).toString();
|
||||
return QLocale(localeStr);
|
||||
};
|
||||
void setAppLanguage(QLocale locale)
|
||||
|
||||
+1413
-802
File diff suppressed because it is too large
Load Diff
+1413
-810
File diff suppressed because it is too large
Load Diff
+1413
-802
File diff suppressed because it is too large
Load Diff
+1416
-801
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1430
-823
File diff suppressed because it is too large
Load Diff
+1410
-803
File diff suppressed because it is too large
Load Diff
+1425
-830
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,7 @@ namespace
|
||||
constexpr char uuid[] = "installation_uuid";
|
||||
constexpr char osVersion[] = "os_version";
|
||||
constexpr char appVersion[] = "app_version";
|
||||
constexpr char appLanguage[] = "app_language";
|
||||
|
||||
constexpr char userCountryCode[] = "user_country_code";
|
||||
constexpr char serverCountryCode[] = "server_country_code";
|
||||
@@ -43,6 +44,9 @@ namespace
|
||||
constexpr char authData[] = "auth_data";
|
||||
|
||||
constexpr char config[] = "config";
|
||||
|
||||
constexpr char subscription[] = "subscription";
|
||||
constexpr char endDate[] = "end_date";
|
||||
}
|
||||
|
||||
struct ProtocolData
|
||||
@@ -163,7 +167,7 @@ namespace
|
||||
auto clientProtocolConfig =
|
||||
QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
|
||||
|
||||
//TODO looks like this block can be removed after v1 configs EOL
|
||||
// TODO looks like this block can be removed after v1 configs EOL
|
||||
|
||||
serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount);
|
||||
serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize);
|
||||
@@ -223,6 +227,19 @@ namespace
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
bool isSubscriptionExpired(const QJsonObject &apiConfig)
|
||||
{
|
||||
auto subscription = apiConfig.value(configKey::subscription).toObject();
|
||||
if (subscription.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
auto subscriptionEndDate = subscription.value(configKey::endDate).toString();
|
||||
if (apiUtils::isSubscriptionExpired(subscriptionEndDate)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel,
|
||||
@@ -242,6 +259,11 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
|
||||
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
|
||||
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||
|
||||
if (isSubscriptionExpired(apiConfigObject)) {
|
||||
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||
return false;
|
||||
}
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getInstallationUuid(true),
|
||||
@@ -277,6 +299,11 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
|
||||
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
|
||||
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||
|
||||
if (isSubscriptionExpired(apiConfigObject)) {
|
||||
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||
return false;
|
||||
}
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getInstallationUuid(true),
|
||||
@@ -322,6 +349,7 @@ bool ApiConfigsController::fillAvailableServices()
|
||||
{
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[configKey::osVersion] = QSysInfo::productType();
|
||||
apiPayload[configKey::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
|
||||
@@ -396,6 +424,11 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
||||
|
||||
if (isSubscriptionExpired(apiConfig)) {
|
||||
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||
return false;
|
||||
}
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getInstallationUuid(true),
|
||||
@@ -429,6 +462,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
||||
|
||||
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
||||
newServerConfig.insert(configKey::authData, gatewayRequestData.authData);
|
||||
newServerConfig.insert(config_key::crc, serverConfig.value(config_key::crc));
|
||||
|
||||
if (serverConfig.value(config_key::nameOverriddenByUser).toBool()) {
|
||||
newServerConfig.insert(config_key::name, serverConfig.value(config_key::name));
|
||||
@@ -502,6 +536,11 @@ bool ApiConfigsController::deactivateDevice()
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isSubscriptionExpired(apiConfigObject)) {
|
||||
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||
return false;
|
||||
}
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_settings->getInstallationUuid(true),
|
||||
@@ -536,6 +575,11 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isSubscriptionExpired(apiConfigObject)) {
|
||||
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||
return false;
|
||||
}
|
||||
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
uuid,
|
||||
|
||||
@@ -297,10 +297,11 @@ void ExportController::revokeConfig(const int row, const DockerContainer contain
|
||||
{
|
||||
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
||||
ErrorCode errorCode =
|
||||
m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex(), serverController);
|
||||
m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex(), serverController);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit exportErrorOccurred(errorCode);
|
||||
}
|
||||
emit revokeConfigCompleted();
|
||||
}
|
||||
|
||||
void ExportController::renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials)
|
||||
|
||||
@@ -42,6 +42,7 @@ public slots:
|
||||
|
||||
signals:
|
||||
void generateConfig(int type);
|
||||
void revokeConfigCompleted();
|
||||
void exportErrorOccurred(const QString &errorMessage);
|
||||
void exportErrorOccurred(ErrorCode errorCode);
|
||||
|
||||
|
||||
@@ -274,7 +274,7 @@ void ImportController::processNativeWireGuardConfig()
|
||||
auto serverProtocolConfig = container.value(ContainerProps::containerTypeToString(DockerContainer::WireGuard)).toObject();
|
||||
auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
|
||||
|
||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(2, 5));
|
||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
|
||||
QString junkPacketMinSize = QString::number(10);
|
||||
QString junkPacketMaxSize = QString::number(50);
|
||||
clientProtocolConfig[config_key::junkPacketCount] = junkPacketCount;
|
||||
@@ -584,26 +584,7 @@ bool ImportController::decodeQrCode(const QString &code)
|
||||
mInstance->m_totalQrCodeChunksCount = 0;
|
||||
mInstance->m_receivedQrCodeChunksCount = 0;
|
||||
}
|
||||
|
||||
// First, try transferController QR (plain JSON payload).
|
||||
QJsonParseError err;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(code.toUtf8(), &err);
|
||||
if (err.error == QJsonParseError::NoError && doc.isObject()) {
|
||||
const QJsonObject obj = doc.object();
|
||||
if (obj.contains(QStringLiteral("gw"))
|
||||
&& (obj.contains(QStringLiteral("uuid")) || obj.contains(QStringLiteral("u")))) {
|
||||
// это наш QR для передачи устройства
|
||||
mInstance->m_isQrCodeProcessed = false;
|
||||
emit mInstance->transferQrDecoded(code);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mInstance->parseQrCodeChunk(code)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return mInstance->parseQrCodeChunk(code);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ signals:
|
||||
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
|
||||
|
||||
void qrDecodingFinished();
|
||||
void transferQrDecoded(const QString &code);
|
||||
|
||||
void restoreAppConfig(const QByteArray &data);
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
||||
containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, protocol));
|
||||
|
||||
if (container == DockerContainer::Awg) {
|
||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(2, 5));
|
||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
|
||||
QString junkPacketMinSize = QString::number(10);
|
||||
QString junkPacketMaxSize = QString::number(50);
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ void ListViewFocusController::previousDelegate()
|
||||
case Section::Default: {
|
||||
if (hasFooter()) {
|
||||
m_currentSection = Section::Footer;
|
||||
viewAtCurrentIndex();
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
@@ -127,9 +128,11 @@ void ListViewFocusController::previousDelegate()
|
||||
case Section::Delegate: {
|
||||
if (m_delegateIndex > 0) {
|
||||
setDelegateIndex(m_delegateIndex - 1);
|
||||
viewAtCurrentIndex();
|
||||
break;
|
||||
} else if (hasHeader()) {
|
||||
m_currentSection = Section::Header;
|
||||
viewAtCurrentIndex();
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
@@ -137,6 +140,7 @@ void ListViewFocusController::previousDelegate()
|
||||
case Section::Header: {
|
||||
m_isReturnNeeded = true;
|
||||
m_currentSection = Section::Default;
|
||||
viewAtCurrentIndex();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -275,7 +279,7 @@ bool ListViewFocusController::isFirstFocusItemInListView() const
|
||||
return isFirstFocusItemInDelegate() && (m_delegateIndex == 0) && !hasHeader();
|
||||
}
|
||||
case Section::Header: {
|
||||
isFirstFocusItemInDelegate();
|
||||
return isFirstFocusItemInDelegate();
|
||||
}
|
||||
case Section::Default: {
|
||||
return true;
|
||||
|
||||
@@ -169,7 +169,7 @@ void PageController::onShowErrorMessage(ErrorCode errorCode)
|
||||
{
|
||||
const auto fullErrorMessage = errorString(errorCode);
|
||||
const auto errorMessage = fullErrorMessage.mid(fullErrorMessage.indexOf(". ") + 1); // remove ErrorCode %1.
|
||||
const auto errorUrl = QStringLiteral("https://docs.amnezia.org/troubleshooting/error-codes/#error-%1-%2").arg(static_cast<int>(errorCode)).arg(utils::enumToString(errorCode).toLower());
|
||||
const auto errorUrl = QStringLiteral("troubleshooting/error-codes/#error-%1-%2").arg(static_cast<int>(errorCode)).arg(utils::enumToString(errorCode).toLower());
|
||||
const auto fullMessage = QStringLiteral("<a href=\"%1\" style=\"color: #FBB26A;\">ErrorCode: %2</a>. %3").arg(errorUrl).arg(static_cast<int>(errorCode)).arg(errorMessage);
|
||||
|
||||
emit showErrorMessage(fullMessage);
|
||||
|
||||
@@ -38,12 +38,9 @@ namespace PageLoader
|
||||
PageSettingsApiInstructions,
|
||||
PageSettingsApiNativeConfigs,
|
||||
PageSettingsApiDevices,
|
||||
PageSettingsApiDevicesLimit,
|
||||
PageSettingsApiAddDeviceConfirm,
|
||||
PageSettingsApiAddDeviceScan,
|
||||
PageSettingsApiSubscriptionKey,
|
||||
PageSettingsKillSwitchExceptions,
|
||||
|
||||
|
||||
PageServiceSftpSettings,
|
||||
PageServiceTorWebsiteSettings,
|
||||
PageServiceDnsSettings,
|
||||
@@ -76,7 +73,6 @@ namespace PageLoader
|
||||
|
||||
PageShareFullAccess,
|
||||
PageShareConnection,
|
||||
PageTransferConfigViaQR,
|
||||
|
||||
PageDevMenu
|
||||
};
|
||||
@@ -129,6 +125,8 @@ signals:
|
||||
void goToPageViewConfig();
|
||||
void goToPageSettingsServerServices();
|
||||
void goToPageSettingsBackup();
|
||||
void goToShareConnectionPage(QString headerText, QString configContentHeaderText, QString configCaption, QString configExtension,
|
||||
QString configFileName);
|
||||
|
||||
void closePage();
|
||||
|
||||
|
||||
@@ -151,6 +151,7 @@ void SettingsController::backupAppConfig(const QString &fileName)
|
||||
config["Conf/autoStart"] = Autostart::isAutostart();
|
||||
config["Conf/killSwitchEnabled"] = isKillSwitchEnabled();
|
||||
config["Conf/strictKillSwitchEnabled"] = isStrictKillSwitchEnabled();
|
||||
config["Conf/useAmneziaDns"] = isAmneziaDnsEnabled();
|
||||
|
||||
SystemController::saveFile(fileName, QJsonDocument(config).toJson());
|
||||
}
|
||||
@@ -186,7 +187,8 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data)
|
||||
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID)
|
||||
int appSplitTunnelingRouteMode = newConfigData.value("Conf/appsRouteMode").toInt();
|
||||
bool appSplittunnelingEnabled = newConfigData.value("Conf/appsSplitTunnelingEnabled").toString().toLower() == "true";
|
||||
bool appSplittunnelingEnabled =
|
||||
newConfigData.value("Conf/appsSplitTunnelingEnabled").toVariant().toString().toLower() == "true";
|
||||
m_appSplitTunnelingModel->setRouteMode(appSplitTunnelingRouteMode);
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
@@ -198,12 +200,13 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data)
|
||||
m_appSplitTunnelingModel->clearAppsList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
m_appSplitTunnelingModel->toggleSplitTunneling(appSplittunnelingEnabled);
|
||||
#endif
|
||||
|
||||
int siteSplitTunnelingRouteMode = newConfigData.value("Conf/routeMode").toInt();
|
||||
bool siteSplittunnelingEnabled = newConfigData.value("Conf/sitesSplitTunnelingEnabled").toString().toLower() == "true";
|
||||
bool siteSplittunnelingEnabled =
|
||||
newConfigData.value("Conf/sitesSplitTunnelingEnabled").toVariant().toString().toLower() == "true";
|
||||
m_sitesModel->setRouteMode(siteSplitTunnelingRouteMode);
|
||||
m_sitesModel->toggleSplitTunneling(siteSplittunnelingEnabled);
|
||||
|
||||
@@ -214,6 +217,11 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data)
|
||||
m_settings->setStrictKillSwitchEnabled(false);
|
||||
#endif
|
||||
|
||||
bool amneziaDnsEnabled = newConfigData.contains("Conf/useAmneziaDns")
|
||||
? newConfigData.value("Conf/useAmneziaDns").toBool()
|
||||
: m_settings->useAmneziaDns();
|
||||
emit amneziaDnsToggled(amneziaDnsEnabled);
|
||||
|
||||
emit restoreBackupFinished();
|
||||
} else {
|
||||
emit changeSettingsErrorOccurred(tr("Backup file is corrupted"));
|
||||
@@ -264,6 +272,9 @@ bool SettingsController::isAutoStartEnabled()
|
||||
void SettingsController::toggleAutoStart(bool enable)
|
||||
{
|
||||
Autostart::setAutostart(enable);
|
||||
if (!enable) {
|
||||
toggleStartMinimized(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool SettingsController::isStartMinimizedEnabled()
|
||||
@@ -274,6 +285,7 @@ bool SettingsController::isStartMinimizedEnabled()
|
||||
void SettingsController::toggleStartMinimized(bool enable)
|
||||
{
|
||||
m_settings->setStartMinimized(enable);
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
|
||||
bool SettingsController::isScreenshotsEnabled()
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
|
||||
|
||||
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
||||
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
||||
|
||||
public slots:
|
||||
void toggleAmneziaDns(bool enable);
|
||||
@@ -125,6 +126,7 @@ signals:
|
||||
void devGatewayEnvChanged(bool enabled);
|
||||
|
||||
void isHomeAdLabelVisibleChanged(bool visible);
|
||||
void startMinimizedChanged();
|
||||
|
||||
private:
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
#include "transferController.h"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QJsonParseError>
|
||||
#include <QDebug>
|
||||
#include <qeventloop.h>
|
||||
#include <QNetworkProxyFactory>
|
||||
#include <QNetworkProxyQuery>
|
||||
#include <QUrl>
|
||||
#include "core/api/apiUtils.h"
|
||||
#include "core/qrCodeUtils.h"
|
||||
|
||||
#include "amnezia_application.h"
|
||||
#include "settings.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/controllers/exportController.h"
|
||||
#include "ui/controllers/importController.h"
|
||||
#include "core/api/apiDefs.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/errorstrings.h"
|
||||
|
||||
namespace {
|
||||
void logSystemProxiesForUrl(const QString &urlStr)
|
||||
{
|
||||
const QUrl url(urlStr);
|
||||
const QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery(QNetworkProxyQuery(url));
|
||||
QStringList proxyDesc;
|
||||
proxyDesc.reserve(proxies.size());
|
||||
for (const auto &p : proxies) {
|
||||
proxyDesc << QStringLiteral("%1 %2:%3")
|
||||
.arg(p.type() == QNetworkProxy::NoProxy ? QStringLiteral("NoProxy")
|
||||
: p.type() == QNetworkProxy::HttpProxy ? QStringLiteral("HttpProxy")
|
||||
: p.type() == QNetworkProxy::Socks5Proxy ? QStringLiteral("Socks5Proxy")
|
||||
: QStringLiteral("Proxy"))
|
||||
.arg(p.hostName())
|
||||
.arg(p.port());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TransferController::TransferController(const std::shared_ptr<Settings> &settings,
|
||||
const QSharedPointer<ServersModel> &serversModel,
|
||||
ExportController *exportController,
|
||||
QObject *parent)
|
||||
: QObject(parent), m_settings(settings), m_serversModel(serversModel), m_exportController(exportController)
|
||||
{
|
||||
}
|
||||
|
||||
void TransferController::handleImportControllerDestroyed()
|
||||
{
|
||||
m_importController = nullptr;
|
||||
stopWaitForConfig();
|
||||
}
|
||||
|
||||
TransferController::~TransferController() {
|
||||
}
|
||||
|
||||
QString TransferController::buildQrPayloadJson(const QString &gatewayUrl, const QString &uuid) const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["gw"] = gatewayUrl;
|
||||
obj["uuid"] = uuid;
|
||||
// Used on the sender side for human-friendly notifications (same style as "Active Devices" list).
|
||||
#if defined(Q_OS_ANDROID)
|
||||
obj["name"] = QStringLiteral("Android");
|
||||
#elif defined(Q_OS_IOS)
|
||||
obj["name"] = QStringLiteral("iOS");
|
||||
#elif defined(Q_OS_WIN)
|
||||
obj["name"] = QStringLiteral("Windows");
|
||||
#elif defined(Q_OS_MACOS)
|
||||
obj["name"] = QStringLiteral("macOS");
|
||||
#elif defined(Q_OS_LINUX)
|
||||
obj["name"] = QStringLiteral("Linux");
|
||||
#else
|
||||
obj["name"] = QStringLiteral("Device");
|
||||
#endif
|
||||
return QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
void TransferController::generateNewQrCode()
|
||||
{
|
||||
QString gw = m_settings->getGatewayEndpoint();
|
||||
if (!gw.endsWith('/')) {
|
||||
gw.append('/');
|
||||
}
|
||||
m_currentUuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
m_currentPayload = buildQrPayloadJson(gw, m_currentUuid);
|
||||
|
||||
auto qr = qrCodeUtils::generateQrCode(m_currentPayload.toUtf8());
|
||||
const QString svg = QString::fromStdString(toSvgString(qr, 1));
|
||||
m_qrCodeUrl = qrCodeUtils::svgToBase64(svg);
|
||||
emit qrCodeUpdated();
|
||||
emit currentUuidChanged();
|
||||
emit currentPayloadChanged();
|
||||
}
|
||||
|
||||
void TransferController::stopScanner()
|
||||
{
|
||||
emit scannerShouldStop();
|
||||
}
|
||||
|
||||
QString TransferController::getCurrentApiKey(QString *vpnKeyOut) const
|
||||
{
|
||||
const int idx = m_serversModel ? m_serversModel->getProcessedServerIndex() : -1;
|
||||
if (idx < 0 || !m_serversModel) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
const QJsonObject server = m_serversModel->getServerConfig(idx);
|
||||
|
||||
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
|
||||
const QJsonObject authData = server.value(QStringLiteral("auth_data")).toObject();
|
||||
|
||||
const QString apiKey = authData.value(QStringLiteral("api_key")).toString();
|
||||
|
||||
if (vpnKeyOut) {
|
||||
QString vpnKey = apiConfig.value(apiDefs::key::vpnKey).toString();
|
||||
if (vpnKey.isEmpty()) {
|
||||
vpnKey = apiUtils::getPremiumV1VpnKey(server);
|
||||
}
|
||||
*vpnKeyOut = vpnKey;
|
||||
}
|
||||
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
void TransferController::onTransferQrScanned(const QString &code)
|
||||
{
|
||||
QJsonParseError err;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(code.toUtf8(), &err);
|
||||
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
qWarning() << "TransferController::onTransferQrScanned: invalid QR JSON " << err.errorString();
|
||||
emit postFailed(QStringLiteral("Invalid QR JSON"));
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject obj = doc.object();
|
||||
QString gw = obj.value("gw").toString();
|
||||
const QString uuid = obj.value("uuid").toString();
|
||||
|
||||
if (gw.isEmpty() || uuid.isEmpty()) {
|
||||
qWarning() << "TransferController::onTransferQrScanned: QR missing gw or uuid";
|
||||
emit postFailed(QStringLiteral("QR missing gw or uuid"));
|
||||
return;
|
||||
}
|
||||
if (!gw.endsWith('/')) {
|
||||
gw.append('/');
|
||||
}
|
||||
|
||||
int chosenServerIdx = -1;
|
||||
QString apiKey;
|
||||
QString vpnKey;
|
||||
|
||||
auto tryServerIndex = [&](int idx) -> bool {
|
||||
if (!m_serversModel || idx < 0 || idx >= m_serversModel->getServersCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QJsonObject server = m_serversModel->getServerConfig(idx);
|
||||
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
|
||||
const QJsonObject authData = server.value(QStringLiteral("auth_data")).toObject();
|
||||
|
||||
const QString candidateApiKey = authData.value(QStringLiteral("api_key")).toString();
|
||||
QString candidateVpnKey = apiConfig.value(apiDefs::key::vpnKey).toString();
|
||||
if (candidateVpnKey.isEmpty()) {
|
||||
// Fallback for older Premium V1 configs where vpn_key may be derived.
|
||||
candidateVpnKey = apiUtils::getPremiumV1VpnKey(server);
|
||||
}
|
||||
|
||||
const bool candidateIsPremium = apiUtils::isPremiumServer(server);
|
||||
const bool candidateIsFromGatewayApi = m_serversModel->data(idx, ServersModel::IsServerFromGatewayApiRole).toBool();
|
||||
|
||||
if (candidateApiKey.isEmpty() || candidateVpnKey.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!candidateIsPremium && !candidateIsFromGatewayApi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
chosenServerIdx = idx;
|
||||
apiKey = candidateApiKey;
|
||||
vpnKey = candidateVpnKey;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (m_serversModel) {
|
||||
tryServerIndex(m_serversModel->getProcessedServerIndex());
|
||||
if (chosenServerIdx < 0) {
|
||||
tryServerIndex(m_serversModel->getDefaultServerIndex());
|
||||
}
|
||||
if (chosenServerIdx < 0) {
|
||||
const int n = m_serversModel->getServersCount();
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (tryServerIndex(i)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chosenServerIdx < 0) {
|
||||
qWarning() << "TransferController::onTransferQrScanned: no suitable subscription key/config found to send";
|
||||
emit postFailed(QStringLiteral("No subscription key or config to send"));
|
||||
return;
|
||||
}
|
||||
|
||||
emit postStarted();
|
||||
|
||||
const int sendTimeoutMs = 60000;
|
||||
GatewayController gatewayController(gw,
|
||||
m_settings->isDevGatewayEnv(),
|
||||
sendTimeoutMs,
|
||||
m_settings->isStrictKillSwitchEnabled());
|
||||
|
||||
QJsonObject payload;
|
||||
payload.insert(QStringLiteral("uuid"), uuid);
|
||||
payload.insert(QStringLiteral("api_key"), apiKey);
|
||||
payload.insert(QStringLiteral("config"), vpnKey);
|
||||
|
||||
const QString endpoint = QStringLiteral("%1v1/sendConfig");
|
||||
QByteArray responseBody;
|
||||
const QString fullUrl = endpoint.arg(gw);
|
||||
qDebug() << "TransferController::onTransferQrScanned: POST" << fullUrl
|
||||
<< "uuid:" << uuid;
|
||||
logSystemProxiesForUrl(fullUrl);
|
||||
const auto errorCode = gatewayController.post(endpoint, payload, responseBody);
|
||||
qDebug() << "TransferController::onTransferQrScanned: sendConfig finished with code"
|
||||
<< static_cast<int>(errorCode)
|
||||
<< "response size:" << responseBody.size();
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
qWarning() << "TransferController::onTransferQrScanned: sendConfig failed with code" << static_cast<int>(errorCode);
|
||||
emit postFailed(QStringLiteral("sendConfig failed: %1").arg(errorString(errorCode)));
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError parseErr;
|
||||
const QJsonDocument respDoc = QJsonDocument::fromJson(responseBody, &parseErr);
|
||||
if (parseErr.error == QJsonParseError::NoError && respDoc.isObject()
|
||||
&& respDoc.object().value(QStringLiteral("status")).toString() == QStringLiteral("success")) {
|
||||
emit postSucceeded();
|
||||
stopScanner();
|
||||
return;
|
||||
}
|
||||
|
||||
qWarning() << "TransferController::onTransferQrScanned: unexpected gateway response:" << responseBody;
|
||||
emit postFailed(QStringLiteral("Gateway response error"));
|
||||
}
|
||||
|
||||
QString TransferController::qrCodeUrl() const
|
||||
{
|
||||
return m_qrCodeUrl;
|
||||
}
|
||||
|
||||
void TransferController::startWaitForConfig(ImportController *importController)
|
||||
{
|
||||
QString gw = m_settings->getGatewayEndpoint();
|
||||
if (!gw.endsWith(QLatin1Char('/'))) {
|
||||
gw.append(QLatin1Char('/'));
|
||||
}
|
||||
|
||||
const QString uuid = m_currentUuid;
|
||||
|
||||
if (uuid.isEmpty()) {
|
||||
qWarning() << "TransferController::startWaitForConfig: no uuid";
|
||||
emit waitError(QStringLiteral("No UUID"));
|
||||
return;
|
||||
}
|
||||
|
||||
m_importController = importController;
|
||||
if (m_importController) {
|
||||
connect(m_importController, &ImportController::destroyed,
|
||||
this,
|
||||
&TransferController::handleImportControllerDestroyed,
|
||||
Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
const int waitTimeoutMs = 60000;
|
||||
|
||||
QJsonObject payload;
|
||||
payload.insert(QStringLiteral("uuid"), uuid);
|
||||
|
||||
GatewayController gatewayController(gw,
|
||||
m_settings->isDevGatewayEnv(),
|
||||
waitTimeoutMs,
|
||||
m_settings->isStrictKillSwitchEnabled());
|
||||
|
||||
const QString endpoint = QStringLiteral("%1v1/waitConfig");
|
||||
QByteArray responseBody;
|
||||
const QString fullUrl = endpoint.arg(gw);
|
||||
logSystemProxiesForUrl(fullUrl);
|
||||
const auto errorCode = gatewayController.post(endpoint, payload, responseBody);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
qWarning() << "TransferController::startWaitForConfig: waitConfig failed with code" << static_cast<int>(errorCode);
|
||||
emit waitError(QStringLiteral("waitConfig failed (%1)").arg(static_cast<int>(errorCode)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_importController) {
|
||||
qWarning() << "TransferController::startWaitForConfig: import controller is null";
|
||||
emit waitError(QStringLiteral("Import Controller destroyed"));
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError parseErr;
|
||||
const QJsonDocument respDoc = QJsonDocument::fromJson(responseBody, &parseErr);
|
||||
if (parseErr.error != QJsonParseError::NoError || !respDoc.isObject()) {
|
||||
qWarning() << "TransferController::startWaitForConfig: invalid JSON response:" << responseBody;
|
||||
emit waitError(QStringLiteral("Invalid gateway response"));
|
||||
return;
|
||||
}
|
||||
const QJsonObject respObj = respDoc.object();
|
||||
const QString status = respObj.value(QStringLiteral("status")).toString();
|
||||
const QString configStr = respObj.value(QStringLiteral("config")).toString();
|
||||
if (status != QStringLiteral("success")) {
|
||||
qWarning() << "TransferController::startWaitForConfig: gateway status not success:" << status;
|
||||
emit waitError(QStringLiteral("Gateway error"));
|
||||
return;
|
||||
}
|
||||
if (configStr.isEmpty()) {
|
||||
emit waitError(QStringLiteral("Empty config"));
|
||||
return;
|
||||
}
|
||||
if (configStr == QStringLiteral("timeout")) {
|
||||
emit waitError(QStringLiteral("Timeout"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_importController->extractConfigFromData(configStr)) {
|
||||
qWarning() << "TransferController::startWaitForConfig: failed to parse config string";
|
||||
emit waitError(QStringLiteral("Invalid config payload"));
|
||||
return;
|
||||
}
|
||||
|
||||
m_importController->importConfig();
|
||||
emit configApplied();
|
||||
}
|
||||
void TransferController::stopWaitForConfig()
|
||||
{
|
||||
qDebug() << "TransferController::stopWaitForConfig: stop flag set";
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
#ifndef TRANSFERCONTROLLER_H
|
||||
#define TRANSFERCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QUuid>
|
||||
|
||||
class Settings;
|
||||
class ServersModel;
|
||||
class ExportController;
|
||||
class ImportController;
|
||||
|
||||
class TransferController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString qrCodeUrl READ qrCodeUrl NOTIFY qrCodeUpdated)
|
||||
Q_PROPERTY(QString pendingQrCode READ pendingQrCode WRITE setPendingQrCode NOTIFY pendingQrCodeChanged)
|
||||
Q_PROPERTY(QString currentUuid READ currentUuid NOTIFY currentUuidChanged)
|
||||
Q_PROPERTY(QString currentPayload READ currentPayload NOTIFY currentPayloadChanged)
|
||||
|
||||
public:
|
||||
explicit TransferController(const std::shared_ptr<Settings> &settings,
|
||||
const QSharedPointer<ServersModel> &serversModel,
|
||||
ExportController *exportController,
|
||||
QObject *parent = nullptr);
|
||||
~TransferController() override;
|
||||
|
||||
Q_INVOKABLE void generateNewQrCode();
|
||||
|
||||
Q_INVOKABLE void stopScanner();
|
||||
Q_INVOKABLE void onTransferQrScanned(const QString &code);
|
||||
|
||||
Q_INVOKABLE void setPendingQrCode(const QString &code) { m_pendingQrCode = code; emit pendingQrCodeChanged(); }
|
||||
QString pendingQrCode() const { return m_pendingQrCode; }
|
||||
|
||||
Q_INVOKABLE void startWaitForConfig(ImportController *importController);
|
||||
Q_INVOKABLE void stopWaitForConfig();
|
||||
|
||||
QString qrCodeUrl() const;
|
||||
QString currentUuid() const { return m_currentUuid; }
|
||||
QString currentPayload() const { return m_currentPayload; }
|
||||
|
||||
signals:
|
||||
void qrCodeUpdated();
|
||||
void scannerShouldStop();
|
||||
void pendingQrCodeChanged();
|
||||
void currentUuidChanged();
|
||||
void currentPayloadChanged();
|
||||
|
||||
void waitError(const QString &message);
|
||||
void configApplied();
|
||||
|
||||
void postStarted();
|
||||
void postSucceeded();
|
||||
void postFailed(const QString &message);
|
||||
|
||||
private slots:
|
||||
void handleImportControllerDestroyed();
|
||||
|
||||
private:
|
||||
QString buildQrPayloadJson(const QString &gatewayUrl, const QString &uuid) const;
|
||||
QString m_pendingQrCode;
|
||||
QString getCurrentApiKey(QString *vpnKeyOut = nullptr) const;
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
ExportController *m_exportController { nullptr };
|
||||
ImportController *m_importController { nullptr };
|
||||
|
||||
QString m_qrCodeUrl;
|
||||
QString m_currentUuid;
|
||||
QString m_currentPayload;
|
||||
};
|
||||
|
||||
#endif // TRANSFERCONTROLLER_H
|
||||
@@ -31,7 +31,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||
return tr("Active");
|
||||
}
|
||||
|
||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active");
|
||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>") : tr("Active");
|
||||
}
|
||||
case EndDateRole: {
|
||||
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace
|
||||
constexpr char serviceInfo[] = "service_info";
|
||||
constexpr char serviceType[] = "service_type";
|
||||
constexpr char serviceProtocol[] = "service_protocol";
|
||||
constexpr char serviceDescription[] = "service_description";
|
||||
|
||||
constexpr char name[] = "name";
|
||||
constexpr char price[] = "price";
|
||||
@@ -22,6 +23,10 @@ namespace
|
||||
constexpr char timelimit[] = "timelimit";
|
||||
constexpr char region[] = "region";
|
||||
|
||||
constexpr char description[] = "description";
|
||||
constexpr char cardDescription[] = "card_description";
|
||||
constexpr char features[] = "features";
|
||||
|
||||
constexpr char availableCountries[] = "available_countries";
|
||||
|
||||
constexpr char storeEndpoint[] = "store_endpoint";
|
||||
@@ -65,11 +70,9 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
||||
case CardDescriptionRole: {
|
||||
auto speed = apiServiceData.serviceInfo.speed;
|
||||
if (serviceType == serviceType::amneziaPremium) {
|
||||
return tr("Amnezia Premium is classic VPN for seamless work, downloading large files, and watching videos. "
|
||||
"Access all websites and online resources. Speeds up to %1 Mbps.")
|
||||
.arg(speed);
|
||||
return apiServiceData.serviceInfo.cardDescription.arg(speed);
|
||||
} else if (serviceType == serviceType::amneziaFree) {
|
||||
QString description = tr("Amnezia Free provides unlimited, free access to a basic set of websites and apps, including Facebook, Instagram, Twitter (X), Discord, Telegram, and more. YouTube is not included in the free plan.");
|
||||
QString description = apiServiceData.serviceInfo.cardDescription;
|
||||
if (!isServiceAvailable) {
|
||||
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, "
|
||||
"return to the previous screen, and try again.</a>");
|
||||
@@ -78,12 +81,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
}
|
||||
case ServiceDescriptionRole: {
|
||||
if (serviceType == serviceType::amneziaPremium) {
|
||||
return tr("Amnezia Premium is classic VPN for for seamless work, downloading large files, and watching videos. "
|
||||
"Access all websites and online resources.");
|
||||
} else {
|
||||
return tr("Amnezia Free provides unlimited, free access to a basic set of websites and apps, including Facebook, Instagram, Twitter (X), Discord, Telegram, and more. YouTube is not included in the free plan.");
|
||||
}
|
||||
return apiServiceData.serviceInfo.description;
|
||||
}
|
||||
case IsServiceAvailableRole: {
|
||||
if (serviceType == serviceType::amneziaFree) {
|
||||
@@ -107,13 +105,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
||||
return apiServiceData.serviceInfo.region;
|
||||
}
|
||||
case FeaturesRole: {
|
||||
if (serviceType == serviceType::amneziaPremium) {
|
||||
return tr("");
|
||||
} else {
|
||||
return tr("VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. "
|
||||
"Other sites will be opened from your real IP address, "
|
||||
"<a href=\"%1\" style=\"color: #FBB26A;\">more details on the website.</a>");
|
||||
}
|
||||
return apiServiceData.serviceInfo.features;
|
||||
}
|
||||
case PriceRole: {
|
||||
auto price = apiServiceData.serviceInfo.price;
|
||||
@@ -125,6 +117,13 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
||||
case EndDateRole: {
|
||||
return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
|
||||
}
|
||||
case OrderRole: {
|
||||
if (serviceType == serviceType::amneziaPremium) {
|
||||
return 0;
|
||||
} else if (serviceType == serviceType::amneziaFree) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
@@ -224,6 +223,7 @@ QHash<int, QByteArray> ApiServicesModel::roleNames() const
|
||||
roles[FeaturesRole] = "features";
|
||||
roles[PriceRole] = "price";
|
||||
roles[EndDateRole] = "endDate";
|
||||
roles[OrderRole] = "order";
|
||||
|
||||
return roles;
|
||||
}
|
||||
@@ -234,6 +234,7 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs
|
||||
auto serviceType = data.value(configKey::serviceType).toString();
|
||||
auto serviceProtocol = data.value(configKey::serviceProtocol).toString();
|
||||
auto availableCountries = data.value(configKey::availableCountries).toArray();
|
||||
auto serviceDescription = data.value(configKey::serviceDescription).toObject();
|
||||
|
||||
auto subscriptionObject = data.value(configKey::subscription).toObject();
|
||||
|
||||
@@ -244,6 +245,10 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs
|
||||
serviceData.serviceInfo.speed = serviceInfo.value(configKey::speed).toString();
|
||||
serviceData.serviceInfo.timeLimit = serviceInfo.value(configKey::timelimit).toString();
|
||||
|
||||
serviceData.serviceInfo.cardDescription = serviceDescription.value(configKey::cardDescription).toString();
|
||||
serviceData.serviceInfo.description = serviceDescription.value(configKey::description).toString();
|
||||
serviceData.serviceInfo.features = serviceDescription.value(configKey::features).toString();
|
||||
|
||||
serviceData.type = serviceType;
|
||||
serviceData.protocol = serviceProtocol;
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ public:
|
||||
RegionRole,
|
||||
FeaturesRole,
|
||||
PriceRole,
|
||||
EndDateRole
|
||||
EndDateRole,
|
||||
OrderRole
|
||||
};
|
||||
|
||||
explicit ApiServicesModel(QObject *parent = nullptr);
|
||||
@@ -58,6 +59,10 @@ private:
|
||||
QString region;
|
||||
QString price;
|
||||
|
||||
QString description;
|
||||
QString features;
|
||||
QString cardDescription;
|
||||
|
||||
QJsonObject object;
|
||||
};
|
||||
|
||||
|
||||
@@ -497,7 +497,8 @@ ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QSt
|
||||
return error;
|
||||
}
|
||||
|
||||
ErrorCode ClientManagementModel::renameClient(const int row, const QString &clientName, const DockerContainer container,
|
||||
ErrorCode ClientManagementModel::renameClient(const int row, const QString &clientName,
|
||||
const DockerContainer container,
|
||||
const ServerCredentials &credentials,
|
||||
const QSharedPointer<ServerController> &serverController, bool addTimeStamp)
|
||||
{
|
||||
@@ -529,7 +530,8 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie
|
||||
return error;
|
||||
}
|
||||
|
||||
ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container, const ServerCredentials &credentials,
|
||||
ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container,
|
||||
const ServerCredentials &credentials,
|
||||
const int serverIndex, const QSharedPointer<ServerController> &serverController)
|
||||
{
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
@@ -44,10 +44,10 @@ public slots:
|
||||
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController);
|
||||
ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
|
||||
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController);
|
||||
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, const ServerCredentials &credentials,
|
||||
const QSharedPointer<ServerController> &serverController, bool addTimeStamp = false);
|
||||
ErrorCode revokeClient(const int index, const DockerContainer container, const ServerCredentials &credentials, const int serverIndex,
|
||||
const QSharedPointer<ServerController> &serverController);
|
||||
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container,
|
||||
const ServerCredentials &credentials, const QSharedPointer<ServerController> &serverController, bool addTimeStamp = false);
|
||||
ErrorCode revokeClient(const int index, const DockerContainer container, const ServerCredentials &credentials,
|
||||
const int serverIndex, const QSharedPointer<ServerController> &serverController);
|
||||
ErrorCode revokeClient(const QJsonObject &containerConfig, const DockerContainer container, const ServerCredentials &credentials,
|
||||
const int serverIndex, const QSharedPointer<ServerController> &serverController);
|
||||
|
||||
@@ -60,6 +60,8 @@ signals:
|
||||
private:
|
||||
bool isClientExists(const QString &clientId);
|
||||
|
||||
int clientIndexById(const QString &clientId);
|
||||
|
||||
void migration(const QByteArray &clientsTableString);
|
||||
|
||||
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, const ServerCredentials &credentials, const int serverIndex,
|
||||
|
||||
@@ -191,14 +191,14 @@ QJsonObject AwgConfigModel::getConfig()
|
||||
jsonConfig[config_key::junkPacketCount] = m_clientProtocolConfig[config_key::junkPacketCount];
|
||||
jsonConfig[config_key::junkPacketMinSize] = m_clientProtocolConfig[config_key::junkPacketMinSize];
|
||||
jsonConfig[config_key::junkPacketMaxSize] = m_clientProtocolConfig[config_key::junkPacketMaxSize];
|
||||
jsonConfig[config_key::specialJunk1] = m_clientProtocolConfig[config_key::specialJunk1];
|
||||
jsonConfig[config_key::specialJunk2] = m_clientProtocolConfig[config_key::specialJunk2];
|
||||
jsonConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3];
|
||||
jsonConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4];
|
||||
jsonConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5];
|
||||
jsonConfig[config_key::controlledJunk1] = m_clientProtocolConfig[config_key::controlledJunk1];
|
||||
jsonConfig[config_key::controlledJunk2] = m_clientProtocolConfig[config_key::controlledJunk2];
|
||||
jsonConfig[config_key::controlledJunk3] = m_clientProtocolConfig[config_key::controlledJunk3];
|
||||
jsonConfig[config_key::specialJunk1] = m_clientProtocolConfig[config_key::specialJunk1].toString().trimmed();
|
||||
jsonConfig[config_key::specialJunk2] = m_clientProtocolConfig[config_key::specialJunk2].toString().trimmed();
|
||||
jsonConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3].toString().trimmed();
|
||||
jsonConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4].toString().trimmed();
|
||||
jsonConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5].toString().trimmed();
|
||||
jsonConfig[config_key::controlledJunk1] = m_clientProtocolConfig[config_key::controlledJunk1].toString().trimmed();
|
||||
jsonConfig[config_key::controlledJunk2] = m_clientProtocolConfig[config_key::controlledJunk2].toString().trimmed();
|
||||
jsonConfig[config_key::controlledJunk3] = m_clientProtocolConfig[config_key::controlledJunk3].toString().trimmed();
|
||||
jsonConfig[config_key::specialHandshakeTimeout] = m_clientProtocolConfig[config_key::specialHandshakeTimeout];
|
||||
|
||||
m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
|
||||
|
||||
@@ -173,6 +173,7 @@ void ServersModel::resetModel()
|
||||
m_servers = m_settings->serversArray();
|
||||
m_defaultServerIndex = m_settings->defaultServerIndex();
|
||||
m_processedServerIndex = m_defaultServerIndex;
|
||||
m_isAmneziaDnsEnabled = m_settings->useAmneziaDns();
|
||||
endResetModel();
|
||||
emit defaultServerIndexChanged(m_defaultServerIndex);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ DrawerType2 {
|
||||
var str = qsTr("We'll preserve all remaining days of your current subscription and give you an extra month as a thank you. ")
|
||||
str += qsTr("This new subscription type will be actively developed with more locations and features added regularly. Currently available:")
|
||||
str += "<ul style='margin-left: -16px;'>"
|
||||
str += qsTr("<li>13 locations (with more coming soon)</li>")
|
||||
str += qsTr("<li>20 locations (with more coming soon)</li>")
|
||||
str += qsTr("<li>Easier switching between countries in the app</li>")
|
||||
str += qsTr("<li>Personal dashboard to manage your subscription</li>")
|
||||
str += "</ul>"
|
||||
|
||||
@@ -20,8 +20,8 @@ Menu {
|
||||
MenuItem {
|
||||
text: qsTr("&Paste")
|
||||
shortcut: StandardKey.Paste
|
||||
// Fix calling paste from clipboard when launching app on android
|
||||
enabled: Qt.platform.os === "android" ? true : textObj.canPaste
|
||||
// Fix calling paste from clipboard when launching app on android/ios
|
||||
enabled: (Qt.platform.os === "android" || Qt.platform.os === "ios") ? true : textObj.canPaste
|
||||
onTriggered: textObj.paste()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,20 @@ import Style 1.0
|
||||
import "TextTypes"
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
property string imageSource
|
||||
property string leftText
|
||||
property var rightText
|
||||
property bool isRightTextUndefined: rightText === undefined
|
||||
property int rightTextFormat: Text.PlainText
|
||||
|
||||
visible: !isRightTextUndefined
|
||||
|
||||
Image {
|
||||
Layout.preferredHeight: 18
|
||||
Layout.preferredWidth: 18
|
||||
source: imageSource
|
||||
source: root.imageSource
|
||||
}
|
||||
|
||||
ListItemTitleType {
|
||||
@@ -25,14 +28,15 @@ RowLayout {
|
||||
Layout.rightMargin: 10
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
text: leftText
|
||||
text: root.leftText
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
visible: rightText !== ""
|
||||
visible: root.rightText !== ""
|
||||
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
text: isRightTextUndefined ? "" : rightText
|
||||
text: root.isRightTextUndefined ? "" : root.rightText
|
||||
textFormat: root.rightTextFormat
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ Popup {
|
||||
Layout.fillWidth: true
|
||||
|
||||
onLinkActivated: function(link) {
|
||||
Qt.openUrlExternally(link)
|
||||
Qt.openUrlExternally(LanguageModel.getCurrentDocsUrl(link))
|
||||
}
|
||||
|
||||
text: root.text
|
||||
|
||||
@@ -155,7 +155,7 @@ Switch {
|
||||
function handleSwitch(event) {
|
||||
if (!event.isAutoRepeat) {
|
||||
root.checked = !root.checked
|
||||
root.checkedChanged()
|
||||
root.toggled()
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
@@ -37,6 +37,22 @@ Item {
|
||||
implicitWidth: content.implicitWidth
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
Keys.onTabPressed: {
|
||||
FocusController.nextKeyTabItem()
|
||||
}
|
||||
|
||||
Keys.onBacktabPressed: {
|
||||
FocusController.previousKeyTabItem()
|
||||
}
|
||||
|
||||
Keys.onUpPressed: {
|
||||
FocusController.nextKeyUpItem()
|
||||
}
|
||||
|
||||
Keys.onDownPressed: {
|
||||
FocusController.nextKeyDownItem()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -153,7 +153,7 @@ PageType {
|
||||
text: qsTr("Auto-negotiate encryption")
|
||||
checked: autoNegotiateEncryprion
|
||||
|
||||
onCheckedChanged: {
|
||||
onToggled: function() {
|
||||
if (checked !== autoNegotiateEncryprion) {
|
||||
autoNegotiateEncryprion = checked
|
||||
}
|
||||
@@ -320,7 +320,7 @@ PageType {
|
||||
|
||||
text: qsTr("Additional client configuration commands")
|
||||
|
||||
onCheckedChanged: {
|
||||
onToggled: function() {
|
||||
if (!checked) {
|
||||
additionalClientCommands = ""
|
||||
}
|
||||
@@ -357,7 +357,7 @@ PageType {
|
||||
|
||||
text: qsTr("Additional server configuration commands")
|
||||
|
||||
onCheckedChanged: {
|
||||
onToggled: function() {
|
||||
if (!checked) {
|
||||
additionalServerCommands = ""
|
||||
}
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool isSending: false
|
||||
|
||||
function getAddedDeviceName() {
|
||||
try {
|
||||
var obj = JSON.parse(TransferController.pendingQrCode)
|
||||
if (obj && obj.name && obj.name.length > 0) {
|
||||
return obj.name
|
||||
}
|
||||
} catch (e) {}
|
||||
return qsTr("Device")
|
||||
}
|
||||
|
||||
function getAvailableCount() {
|
||||
var max = ApiAccountInfoModel.data("maxDeviceCount")
|
||||
var active = ApiAccountInfoModel.data("activeDeviceCount")
|
||||
if (!max || max <= 0) max = 7
|
||||
if (!active || active < 0) active = 0
|
||||
var remain = max - active
|
||||
return remain > 0 ? remain : 0
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 20
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BackButtonType {
|
||||
backButtonFunction: function() {
|
||||
if (root.isSending) {
|
||||
TransferController.stopWaitForConfig()
|
||||
}
|
||||
PageController.closePage()
|
||||
}
|
||||
}
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
headerText: qsTr("Add a new device to the subscription?")
|
||||
descriptionText: qsTr("Devices available with Amnezia Premium: (%1)").arg(getAvailableCount())
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
|
||||
text: qsTr("Add Device")
|
||||
enabled: !root.isSending && root.getAvailableCount() > 0 && TransferController.pendingQrCode !== ""
|
||||
|
||||
clickedFunc: function() {
|
||||
if (TransferController.pendingQrCode !== "") {
|
||||
root.isSending = true
|
||||
TransferController.onTransferQrScanned(TransferController.pendingQrCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
textColor: AmneziaStyle.color.paleGray
|
||||
borderColor: AmneziaStyle.color.paleGray
|
||||
borderWidth: 1
|
||||
|
||||
text: qsTr("Cancel")
|
||||
enabled: !root.isSending
|
||||
|
||||
clickedFunc: function() {
|
||||
PageController.closePage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TransferController
|
||||
|
||||
function onPostStarted() {
|
||||
PageController.showNotificationMessage(qsTr("Sending configuration..."))
|
||||
}
|
||||
|
||||
function onPostSucceeded() {
|
||||
root.isSending = false
|
||||
PageController.showNotificationMessage(qsTr("%1 has been added to your subscription").arg(root.getAddedDeviceName()))
|
||||
PageController.closePage()
|
||||
PageController.closePage()
|
||||
}
|
||||
|
||||
function onPostFailed(message) {
|
||||
root.isSending = false
|
||||
PageController.showErrorMessage(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
|
||||
import PageEnum 1.0
|
||||
import QRCodeReader 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool scanCompleted: false
|
||||
|
||||
Item {
|
||||
id: cameraArea
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: iosQrLoader
|
||||
anchors.fill: cameraArea
|
||||
active: Qt.platform.os === "ios"
|
||||
|
||||
sourceComponent: Component {
|
||||
QRCodeReader {
|
||||
id: qrCodeReader
|
||||
|
||||
function updateCameraSize() {
|
||||
qrCodeReader.setCameraSize(Qt.rect(cameraArea.x,
|
||||
cameraArea.y,
|
||||
cameraArea.width,
|
||||
cameraArea.height))
|
||||
}
|
||||
|
||||
onCodeReaded: function(code) {
|
||||
if (!code || code.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
var obj = null
|
||||
try {
|
||||
obj = JSON.parse(code)
|
||||
} catch (e) {
|
||||
obj = null
|
||||
}
|
||||
|
||||
if (!obj || !obj.gw || !(obj.uuid || obj.u)) {
|
||||
return
|
||||
}
|
||||
|
||||
var normalizedObj = { gw: obj.gw, uuid: (obj.uuid ? obj.uuid : obj.u) }
|
||||
if (obj.name && obj.name.length > 0) {
|
||||
normalizedObj.name = obj.name
|
||||
}
|
||||
var normalized = JSON.stringify(normalizedObj)
|
||||
TransferController.setPendingQrCode(normalized)
|
||||
qrCodeReader.stopReading()
|
||||
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceConfirm)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateCameraSize()
|
||||
qrCodeReader.startReading()
|
||||
}
|
||||
Component.onDestruction: qrCodeReader.stopReading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onWidthChanged: {
|
||||
if (iosQrLoader.item && iosQrLoader.item.updateCameraSize) {
|
||||
iosQrLoader.item.updateCameraSize()
|
||||
}
|
||||
}
|
||||
onHeightChanged: {
|
||||
if (iosQrLoader.item && iosQrLoader.item.updateCameraSize) {
|
||||
iosQrLoader.item.updateCameraSize()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TransferController
|
||||
function onScannerShouldStop() {
|
||||
if (iosQrLoader.item && iosQrLoader.item.stopReading) {
|
||||
iosQrLoader.item.stopReading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ImportController
|
||||
|
||||
function onTransferQrDecoded(code) {
|
||||
if (!code || code.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
var obj = null
|
||||
try {
|
||||
obj = JSON.parse(code)
|
||||
} catch (e) {
|
||||
obj = null
|
||||
}
|
||||
if (obj && obj.gw && (obj.uuid || obj.u)) {
|
||||
var normalizedObj = { gw: obj.gw, uuid: (obj.uuid ? obj.uuid : obj.u) }
|
||||
if (obj.name && obj.name.length > 0) {
|
||||
normalizedObj.name = obj.name
|
||||
}
|
||||
code = JSON.stringify(normalizedObj)
|
||||
}
|
||||
|
||||
root.scanCompleted = true
|
||||
TransferController.setPendingQrCode(code)
|
||||
Qt.callLater(function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceConfirm)
|
||||
})
|
||||
}
|
||||
|
||||
function onQrDecodingFinished() {
|
||||
if (Qt.platform.os === "android" && !root.scanCompleted) {
|
||||
PageController.closePage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
TransferController.setPendingQrCode("")
|
||||
root.scanCompleted = false
|
||||
|
||||
if (Qt.platform.os === "android") {
|
||||
ImportController.startDecodingQr()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
|
||||
import QtCore
|
||||
import QRCodeReader 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
@@ -20,29 +19,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
function isAtDeviceLimit() {
|
||||
var maxDeviceCount = ApiAccountInfoModel.data("maxDeviceCount")
|
||||
return listView.count >= maxDeviceCount
|
||||
}
|
||||
|
||||
function getConfigFilesCount() {
|
||||
try {
|
||||
var arr = ApiAccountInfoModel.getIssuedConfigsInfo()
|
||||
if (!arr) return 0
|
||||
|
||||
var count = 0
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var item = arr[i]
|
||||
if (item && item["source_type"] === "country_config") {
|
||||
++count
|
||||
}
|
||||
}
|
||||
return count
|
||||
} catch (e) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
@@ -70,41 +46,16 @@ PageType {
|
||||
descriptionText: qsTr("Manage currently connected devices")
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: addDeviceQrButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
WarningType {
|
||||
Layout.topMargin: 16
|
||||
|
||||
visible: GC.isMobile()
|
||||
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
textColor: AmneziaStyle.color.paleGray
|
||||
borderColor: AmneziaStyle.color.paleGray
|
||||
borderWidth: 1
|
||||
|
||||
text: qsTr("Add device by QR code")
|
||||
|
||||
clickedFunc: function() {
|
||||
if (root.isAtDeviceLimit()) {
|
||||
PageController.goToPage(PageEnum.PageSettingsApiDevicesLimit)
|
||||
} else {
|
||||
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceScan)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SmallTextType {
|
||||
Layout.topMargin: 8
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("On the other device, tap + at the bottom → Connect to Amnezia Premium")
|
||||
textString: qsTr("You can find the identifier on the Support tab or, for older versions of the app, "
|
||||
+ "by tapping '+' and then the three dots at the top of the page.")
|
||||
|
||||
iconPath: "qrc:/images/controls/alert-circle.svg"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,58 +93,6 @@ PageType {
|
||||
|
||||
DividerType {}
|
||||
}
|
||||
|
||||
footer: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 6
|
||||
|
||||
text: qsTr("Configuration Files: {%1}").arg(root.getConfigFilesCount())
|
||||
descriptionText: qsTr("Generated configuration files also count towards the device limit")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
ApiSettingsController.updateApiCountryModel()
|
||||
PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
WarningType {
|
||||
Layout.topMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.bottomMargin: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
textString: qsTr("The Support tag is available on the Support page. In older versions: tap + in the bottom bar, then More (...) in the top-right.")
|
||||
iconPath: "qrc:/images/controls/alert-circle.svg"
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TransferController
|
||||
|
||||
function onPostStarted() {
|
||||
PageController.showBusyIndicator(true)
|
||||
}
|
||||
|
||||
function onPostSucceeded() {
|
||||
PageController.showBusyIndicator(false)
|
||||
ApiSettingsController.getAccountInfo(true)
|
||||
PageController.showNotificationMessage(qsTr("New device added to subscription"))
|
||||
}
|
||||
|
||||
function onPostFailed(message) {
|
||||
PageController.showBusyIndicator(false)
|
||||
PageController.showErrorMessage(message)
|
||||
}
|
||||
|
||||
function onScannerShouldStop() {}
|
||||
}
|
||||
}
|
||||
|
||||
function deactivateExternalDevice(supportTag, countryCode) {
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 20
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BackButtonType {}
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
headerText: qsTr("Your Amnezia Premium subscription already has the maximum number of devices — ") + ApiAccountInfoModel.data("connectedDevices")
|
||||
descriptionText: qsTr("Remove one of the previously connected devices to add a new one")
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
|
||||
text: qsTr("Show all devices")
|
||||
|
||||
clickedFunc: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsApiDevices)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ PageType {
|
||||
readonly property string title: qsTr("Subscription Status")
|
||||
readonly property string contentKey: "subscriptionStatus"
|
||||
readonly property string objectImageSource: "qrc:/images/controls/info.svg"
|
||||
readonly property bool isRichText: true
|
||||
}
|
||||
|
||||
QtObject {
|
||||
@@ -37,6 +38,7 @@ PageType {
|
||||
readonly property string title: qsTr("Valid Until")
|
||||
readonly property string contentKey: "endDate"
|
||||
readonly property string objectImageSource: "qrc:/images/controls/history.svg"
|
||||
readonly property bool isRichText: false
|
||||
}
|
||||
|
||||
QtObject {
|
||||
@@ -45,6 +47,7 @@ PageType {
|
||||
readonly property string title: qsTr("Active Connections")
|
||||
readonly property string contentKey: "connectedDevices"
|
||||
readonly property string objectImageSource: "qrc:/images/controls/monitor.svg"
|
||||
readonly property bool isRichText: false
|
||||
}
|
||||
|
||||
property var processedServer
|
||||
@@ -104,7 +107,7 @@ PageType {
|
||||
|
||||
actionButtonImage: "qrc:/images/controls/edit-3.svg"
|
||||
|
||||
headerText: root.processedServer && root.processedServer.name ? root.processedServer.name : ""
|
||||
headerText: root.processedServer.name
|
||||
descriptionText: ApiAccountInfoModel.data("serviceDescription")
|
||||
|
||||
actionButtonFunction: function() {
|
||||
@@ -134,6 +137,7 @@ PageType {
|
||||
imageSource: objectImageSource
|
||||
leftText: title
|
||||
rightText: ApiAccountInfoModel.data(contentKey)
|
||||
rightTextFormat: isRichText ? Text.RichText : Text.PlainText
|
||||
|
||||
visible: rightText !== ""
|
||||
}
|
||||
@@ -413,6 +417,6 @@ PageType {
|
||||
anchors.fill: parent
|
||||
expandedHeight: parent.height * 0.35
|
||||
|
||||
serverNameText: root.processedServer && root.processedServer.name ? root.processedServer.name : ""
|
||||
serverNameText: root.processedServer.name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import Qt.labs.platform 1.1
|
||||
|
||||
import QtCore
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
@@ -17,6 +19,33 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property var processedServer
|
||||
|
||||
Connections {
|
||||
target: ServersModel
|
||||
|
||||
function onProcessedServerChanged() {
|
||||
root.processedServer = proxyServersModel.get(0)
|
||||
}
|
||||
}
|
||||
|
||||
SortFilterProxyModel {
|
||||
id: proxyServersModel
|
||||
objectName: "proxyServersModel"
|
||||
|
||||
sourceModel: ServersModel
|
||||
filters: [
|
||||
ValueFilter {
|
||||
roleName: "isCurrentlyProcessed"
|
||||
value: true
|
||||
}
|
||||
]
|
||||
|
||||
Component.onCompleted: {
|
||||
root.processedServer = proxyServersModel.get(0)
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
PageController.showBusyIndicator(true)
|
||||
ApiConfigsController.prepareVpnKeyExport()
|
||||
@@ -40,7 +69,7 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
text: qsTr("Amnezia Premium\nsubscription key")
|
||||
text: qsTr(root.processedServer.name + "\nsubscription key")
|
||||
font.pixelSize: 32
|
||||
font.bold: true
|
||||
color: AmneziaStyle.color.paleGray
|
||||
@@ -53,18 +82,10 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
defaultColor: AmneziaStyle.color.paleGray
|
||||
hoveredColor: AmneziaStyle.color.sheerWhite
|
||||
pressedColor: AmneziaStyle.color.translucentWhite
|
||||
disabledColor: AmneziaStyle.color.mutedGray
|
||||
textColor: AmneziaStyle.color.black
|
||||
leftImageColor: "black"
|
||||
borderWidth: 1
|
||||
|
||||
text: qsTr("Copy key")
|
||||
leftImageSource: "qrc:/images/controls/copy.svg"
|
||||
|
||||
onClicked: {
|
||||
clickedFunc: function() {
|
||||
ApiConfigsController.copyVpnKeyToClipboard()
|
||||
PageController.showNotificationMessage(qsTr("Copied"))
|
||||
}
|
||||
@@ -85,13 +106,13 @@ PageType {
|
||||
text: qsTr("Save key as a file")
|
||||
leftImageSource: "qrc:/images/controls/share-2.svg"
|
||||
|
||||
onClicked: {
|
||||
clickedFunc: function() {
|
||||
var fileName = GC.isMobile()
|
||||
? "amnezia_vpn_key.vpn"
|
||||
? root.processedServer.name.toLowerCase().replace(/\s+/g, "_") + "_key.vpn"
|
||||
: SystemController.getFileName(
|
||||
qsTr("Save AmneziaVPN config"),
|
||||
qsTr("Config files (*.vpn)"),
|
||||
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/amnezia_vpn_key",
|
||||
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + root.processedServer.name.toLowerCase().replace(/\s+/g, "_") + "_key",
|
||||
true,
|
||||
".vpn"
|
||||
)
|
||||
@@ -118,7 +139,7 @@ PageType {
|
||||
text: qsTr("Show key text")
|
||||
leftImageSource: "qrc:/images/controls/eye.svg"
|
||||
|
||||
onClicked: {
|
||||
clickedFunc: function() {
|
||||
PageController.showBusyIndicator(true)
|
||||
ApiConfigsController.prepareVpnKeyExport()
|
||||
PageController.showBusyIndicator(false)
|
||||
@@ -127,8 +148,9 @@ PageType {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: width
|
||||
Layout.preferredWidth: Math.min(Math.min(root.width - (Layout.leftMargin + Layout.rightMargin), root.height * 0.5), 360)
|
||||
Layout.preferredHeight: Layout.preferredWidth
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 20
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
@@ -140,6 +162,9 @@ PageType {
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
smooth: false
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize.width: parent.width
|
||||
sourceSize.height: parent.height
|
||||
source: ApiConfigsController.qrCodesCount > 0 && ApiConfigsController.qrCodes[0] ? ApiConfigsController.qrCodes[0] : ""
|
||||
}
|
||||
}
|
||||
@@ -181,7 +206,7 @@ PageType {
|
||||
|
||||
Header2Type {
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("Amnezia Premium Subscription key")
|
||||
headerText: qsTr(root.processedServer.name + " Subscription key")
|
||||
}
|
||||
|
||||
TextArea {
|
||||
@@ -194,7 +219,7 @@ PageType {
|
||||
font.pixelSize: 16
|
||||
font.weight: Font.Medium
|
||||
font.family: "PT Root UI VF"
|
||||
text: ApiConfigsController.vpnKey //|| ""
|
||||
text: ApiConfigsController.vpnKey
|
||||
wrapMode: Text.Wrap
|
||||
background: Rectangle { color: AmneziaStyle.color.transparent }
|
||||
}
|
||||
|
||||
@@ -145,11 +145,25 @@ PageType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WarningType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
textString: qsTr("Only \"Apps from the list should not have access via VPN\" mode is available on Windows")
|
||||
iconPath: "qrc:/images/controls/alert-circle.svg"
|
||||
|
||||
enabled: (Qt.platform.os === "windows") && root.pageEnabled
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
ScrollBar.vertical: ScrollBarType { policy: ScrollBar.AlwaysOn }
|
||||
|
||||
anchors.top: header.bottom
|
||||
anchors.bottom: addAppButton.top
|
||||
anchors.left: parent.left
|
||||
|
||||
@@ -66,7 +66,7 @@ PageType {
|
||||
text: qsTr("Allow application screenshots")
|
||||
|
||||
checked: SettingsController.isScreenshotsEnabled()
|
||||
onCheckedChanged: {
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.isScreenshotsEnabled()) {
|
||||
SettingsController.toggleScreenshotsEnabled(checked)
|
||||
}
|
||||
@@ -109,7 +109,7 @@ PageType {
|
||||
descriptionText: qsTr("Launch the application every time the device is starts")
|
||||
|
||||
checked: SettingsController.isAutoStartEnabled()
|
||||
onCheckedChanged: {
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.isAutoStartEnabled()) {
|
||||
SettingsController.toggleAutoStart(checked)
|
||||
}
|
||||
@@ -132,7 +132,7 @@ PageType {
|
||||
descriptionText: qsTr("Connect to VPN on app start")
|
||||
|
||||
checked: SettingsController.isAutoConnectEnabled()
|
||||
onCheckedChanged: {
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.isAutoConnectEnabled()) {
|
||||
SettingsController.toggleAutoConnect(checked)
|
||||
}
|
||||
@@ -157,9 +157,9 @@ PageType {
|
||||
enabled: switcherAutoStart.checked
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
|
||||
checked: SettingsController.isStartMinimizedEnabled()
|
||||
onCheckedChanged: {
|
||||
if (checked !== SettingsController.isStartMinimizedEnabled()) {
|
||||
checked: SettingsController.startMinimized
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.startMinimized) {
|
||||
SettingsController.toggleStartMinimized(checked)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ PageType {
|
||||
descriptionText: qsTr("If AmneziaDNS is installed on the server")
|
||||
|
||||
checked: SettingsController.isAmneziaDnsEnabled()
|
||||
onCheckedChanged: {
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.isAmneziaDnsEnabled()) {
|
||||
SettingsController.toggleAmneziaDns(checked)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ PageType {
|
||||
|
||||
checked: SettingsController.isLoggingEnabled
|
||||
|
||||
onCheckedChanged: {
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.isLoggingEnabled) {
|
||||
SettingsController.isLoggingEnabled = checked
|
||||
}
|
||||
@@ -167,7 +167,8 @@ PageType {
|
||||
|
||||
// Show service logs only if this is NOT a macOS build with
|
||||
// Network-Extension (IsMacOsNeBuild is injected from C++ at run-time)
|
||||
property list<QtObject> logTypes: IsMacOsNeBuild ? [
|
||||
// or if this is NOT a mobile build
|
||||
property list<QtObject> logTypes: (IsMacOsNeBuild || GC.isMobile()) ? [
|
||||
clientLogs
|
||||
] : [
|
||||
clientLogs,
|
||||
@@ -214,15 +215,11 @@ PageType {
|
||||
}
|
||||
readonly property var exportLogsHandler: function() {
|
||||
var fileName = ""
|
||||
if (GC.isMobile()) {
|
||||
fileName = "AmneziaVPN-service.log"
|
||||
} else {
|
||||
fileName = SystemController.getFileName(qsTr("Save"),
|
||||
qsTr("Logs files (*.log)"),
|
||||
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN-service",
|
||||
true,
|
||||
".log")
|
||||
}
|
||||
fileName = SystemController.getFileName(qsTr("Save"),
|
||||
qsTr("Logs files (*.log)"),
|
||||
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN-service",
|
||||
true,
|
||||
".log")
|
||||
if (fileName !== "") {
|
||||
PageController.showBusyIndicator(true)
|
||||
SettingsController.exportServiceLogsFile(fileName)
|
||||
|
||||
@@ -164,6 +164,8 @@ PageType {
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
ScrollBar.vertical: ScrollBarType { policy: ScrollBar.AlwaysOn }
|
||||
|
||||
anchors.top: header.bottom
|
||||
anchors.topMargin: 16
|
||||
anchors.bottom: addSiteButton.top
|
||||
|
||||
@@ -66,6 +66,8 @@ PageType {
|
||||
imageSource: imagePath
|
||||
leftText: lText
|
||||
rightText: rText
|
||||
|
||||
visible: isVisible
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user