amnezia-client/client/ui/controllers/installController.cpp
2024-02-19 14:54:15 +00:00

516 lines
21 KiB
C++

#include "installController.h"
#include <QDesktopServices>
#include <QDir>
#include <QEventLoop>
#include <QJsonObject>
#include <QStandardPaths>
#include <QRandomGenerator>
#include "core/errorstrings.h"
#include "core/controllers/serverController.h"
#include "utilities.h"
namespace
{
#ifdef Q_OS_WINDOWS
QString getNextDriverLetter()
{
QProcess drivesProc;
drivesProc.start("wmic logicaldisk get caption");
drivesProc.waitForFinished();
QString drives = drivesProc.readAll();
qDebug() << drives;
QString letters = "CFGHIJKLMNOPQRSTUVWXYZ";
QString letter;
for (int i = letters.size() - 1; i > 0; i--) {
letter = letters.at(i);
if (!drives.contains(letter + ":"))
break;
}
if (letter == "C:") {
// set err info
qDebug() << "Can't find free drive letter";
return "";
}
return letter;
}
#endif
}
InstallController::InstallController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<ProtocolsModel> &protocolsModel,
const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent),
m_serversModel(serversModel),
m_containersModel(containersModel),
m_protocolModel(protocolsModel),
m_settings(settings)
{
}
InstallController::~InstallController()
{
#ifdef Q_OS_WINDOWS
for (QSharedPointer<QProcess> process : m_sftpMountProcesses) {
Utils::signalCtrl(process->processId(), CTRL_C_EVENT);
process->kill();
process->waitForFinished();
}
#endif
}
void InstallController::install(DockerContainer container, int port, TransportProto transportProto)
{
QJsonObject config;
auto mainProto = ContainerProps::defaultProtocol(container);
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
QJsonObject containerConfig;
if (protocol == mainProto) {
containerConfig.insert(config_key::port, QString::number(port));
containerConfig.insert(config_key::transport_proto,
ProtocolProps::transportProtoToString(transportProto, protocol));
if (container == DockerContainer::Awg) {
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(3, 10));
QString junkPacketMinSize = QString::number(50);
QString junkPacketMaxSize = QString::number(1000);
QString initPacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150));
QString responsePacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150));
QSet<QString> headersValue;
while (headersValue.size() != 4) {
auto max = (std::numeric_limits<qint32>::max)();
headersValue.insert(QString::number(QRandomGenerator::global()->bounded(1, max)));
}
auto headersValueList = headersValue.values();
QString initPacketMagicHeader = headersValueList.at(0);
QString responsePacketMagicHeader = headersValueList.at(1);
QString underloadPacketMagicHeader = headersValueList.at(2);
QString transportPacketMagicHeader = headersValueList.at(3);
containerConfig[config_key::junkPacketCount] = junkPacketCount;
containerConfig[config_key::junkPacketMinSize] = junkPacketMinSize;
containerConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize;
containerConfig[config_key::initPacketJunkSize] = initPacketJunkSize;
containerConfig[config_key::responsePacketJunkSize] = responsePacketJunkSize;
containerConfig[config_key::initPacketMagicHeader] = initPacketMagicHeader;
containerConfig[config_key::responsePacketMagicHeader] = responsePacketMagicHeader;
containerConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader;
containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader;
}
if (container == DockerContainer::Sftp) {
containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName);
containerConfig.insert(config_key::password, Utils::getRandomString(10));
}
config.insert(config_key::container, ContainerProps::containerToString(container));
}
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
}
if (m_shouldCreateServer) {
if (isServerAlreadyExists()) {
return;
}
installServer(container, config);
} else {
installContainer(container, config);
}
}
void InstallController::installServer(DockerContainer container, QJsonObject &config)
{
ServerController serverController(m_settings);
connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy);
connect(this, &InstallController::cancelInstallation, &serverController, &ServerController::cancelInstallation);
QMap<DockerContainer, QJsonObject> installedContainers;
ErrorCode errorCode =
serverController.getAlreadyInstalledContainers(m_currentlyInstalledServerCredentials, installedContainers);
QString finishMessage = "";
if (!installedContainers.contains(container)) {
errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config);
installedContainers.insert(container, config);
finishMessage = tr("%1 installed successfully. ").arg(ContainerProps::containerHumanNames().value(container));
} else {
finishMessage = tr("%1 is already installed on the server. ").arg(ContainerProps::containerHumanNames().value(container));
}
if (installedContainers.size() > 1) {
finishMessage += tr("\nAdded containers that were already installed on the server");
}
if (errorCode == ErrorCode::NoError) {
QJsonObject server;
server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName);
server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName);
server.insert(config_key::password, m_currentlyInstalledServerCredentials.secretData);
server.insert(config_key::port, m_currentlyInstalledServerCredentials.port);
server.insert(config_key::description, m_settings->nextAvailableServerName());
QJsonArray containerConfigs;
for (const QJsonObject &containerConfig : qAsConst(installedContainers)) {
containerConfigs.append(containerConfig);
}
server.insert(config_key::containers, containerConfigs);
server.insert(config_key::defaultContainer, ContainerProps::containerToString(container));
m_serversModel->addServer(server);
emit installServerFinished(finishMessage);
return;
}
emit installationErrorOccurred(errorString(errorCode));
}
void InstallController::installContainer(DockerContainer container, QJsonObject &config)
{
int serverIndex = m_serversModel->getProcessedServerIndex();
ServerCredentials serverCredentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
ServerController serverController(m_settings);
connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy);
connect(this, &InstallController::cancelInstallation, &serverController, &ServerController::cancelInstallation);
QMap<DockerContainer, QJsonObject> installedContainers;
ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers);
QString finishMessage = "";
if (!installedContainers.contains(container)) {
errorCode = serverController.setupContainer(serverCredentials, container, config);
installedContainers.insert(container, config);
finishMessage = tr("%1 installed successfully. ").arg(ContainerProps::containerHumanNames().value(container));
} else {
finishMessage = tr("%1 is already installed on the server. ").arg(ContainerProps::containerHumanNames().value(container));
}
bool isInstalledContainerAddedToGui = false;
if (errorCode == ErrorCode::NoError) {
for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) {
QJsonObject containerConfig = m_containersModel->getContainerConfig(iterator.key());
if (containerConfig.isEmpty()) {
m_serversModel->addContainerConfig(iterator.key(), iterator.value());
if (container != iterator.key()) { // skip the newly installed container
isInstalledContainerAddedToGui = true;
}
}
}
if (isInstalledContainerAddedToGui) {
finishMessage += tr("\nAlready installed containers were found on the server. "
"All installed containers have been added to the application");
}
emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other);
return;
}
emit installationErrorOccurred(errorString(errorCode));
}
bool InstallController::isServerAlreadyExists()
{
for (int i = 0; i < m_serversModel->getServersCount(); i++) {
auto modelIndex = m_serversModel->index(i);
const ServerCredentials credentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(modelIndex, ServersModel::Roles::CredentialsRole));
if (m_currentlyInstalledServerCredentials.hostName == credentials.hostName
&& m_currentlyInstalledServerCredentials.port == credentials.port) {
emit serverAlreadyExists(i);
return true;
}
}
return false;
}
void InstallController::scanServerForInstalledContainers()
{
int serverIndex = m_serversModel->getProcessedServerIndex();
ServerCredentials serverCredentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
ServerController serverController(m_settings);
QMap<DockerContainer, QJsonObject> installedContainers;
ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers);
if (errorCode == ErrorCode::NoError) {
bool isInstalledContainerAddedToGui = false;
for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) {
QJsonObject containerConfig = m_containersModel->getContainerConfig(iterator.key());
if (containerConfig.isEmpty()) {
m_serversModel->addContainerConfig(iterator.key(), iterator.value());
isInstalledContainerAddedToGui = true;
}
}
emit scanServerFinished(isInstalledContainerAddedToGui);
return;
}
emit installationErrorOccurred(errorString(errorCode));
}
void InstallController::updateContainer(QJsonObject config)
{
int serverIndex = m_serversModel->getProcessedServerIndex();
ServerCredentials serverCredentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
const DockerContainer container = ContainerProps::containerFromString(config.value(config_key::container).toString());
QJsonObject oldContainerConfig = m_containersModel->getContainerConfig(container);
ServerController serverController(m_settings);
connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy);
connect(this, &InstallController::cancelInstallation, &serverController, &ServerController::cancelInstallation);
auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config);
if (errorCode == ErrorCode::NoError) {
m_serversModel->updateContainerConfig(container, config);
m_protocolModel->updateModel(config);
auto defaultContainer = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
if ((serverIndex == m_serversModel->getDefaultServerIndex()) && (container == defaultContainer)) {
emit currentContainerUpdated();
} else {
emit updateContainerFinished(tr("Settings updated successfully"));
}
return;
}
emit installationErrorOccurred(errorString(errorCode));
}
void InstallController::rebootProcessedServer()
{
int serverIndex = m_serversModel->getProcessedServerIndex();
QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString();
m_serversModel->rebootServer();
emit rebootProcessedServerFinished(tr("Server '%1' was rebooted").arg(serverName));
}
void InstallController::removeProcessedServer()
{
int serverIndex = m_serversModel->getProcessedServerIndex();
QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString();
m_serversModel->removeServer();
emit removeProcessedServerFinished(tr("Server '%1' was removed").arg(serverName));
}
void InstallController::removeAllContainers()
{
int serverIndex = m_serversModel->getProcessedServerIndex();
QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString();
ErrorCode errorCode = m_serversModel->removeAllContainers();
if (errorCode == ErrorCode::NoError) {
emit removeAllContainersFinished(tr("All containers from server '%1' have been removed").arg(serverName));
return;
}
emit installationErrorOccurred(errorString(errorCode));
}
void InstallController::removeCurrentlyProcessedContainer()
{
int serverIndex = m_serversModel->getProcessedServerIndex();
QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString();
int container = m_containersModel->getCurrentlyProcessedContainerIndex();
QString containerName = m_containersModel->getCurrentlyProcessedContainerName();
ErrorCode errorCode = m_serversModel->removeContainer(container);
if (errorCode == ErrorCode::NoError) {
emit removeCurrentlyProcessedContainerFinished(tr("%1 has been removed from the server '%2'").arg(containerName, serverName));
return;
}
emit installationErrorOccurred(errorString(errorCode));
}
QRegularExpression InstallController::ipAddressPortRegExp()
{
return Utils::ipAddressPortRegExp();
}
QRegularExpression InstallController::ipAddressRegExp()
{
return Utils::ipAddressRegExp();
}
void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName,
const QString &secretData)
{
m_currentlyInstalledServerCredentials.hostName = hostName;
if (m_currentlyInstalledServerCredentials.hostName.contains(":")) {
m_currentlyInstalledServerCredentials.port =
m_currentlyInstalledServerCredentials.hostName.split(":").at(1).toInt();
m_currentlyInstalledServerCredentials.hostName = m_currentlyInstalledServerCredentials.hostName.split(":").at(0);
}
m_currentlyInstalledServerCredentials.userName = userName;
m_currentlyInstalledServerCredentials.secretData = secretData;
}
void InstallController::setShouldCreateServer(bool shouldCreateServer)
{
m_shouldCreateServer = shouldCreateServer;
}
void InstallController::mountSftpDrive(const QString &port, const QString &password, const QString &username)
{
QString mountPath;
QString cmd;
int serverIndex = m_serversModel->getProcessedServerIndex();
ServerCredentials serverCredentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
QString hostname = serverCredentials.hostName;
#ifdef Q_OS_WINDOWS
mountPath = getNextDriverLetter() + ":";
// QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3")
// .arg(labelTftpUserNameText())
// .arg(labelTftpPortText())
// .arg(labelTftpPasswordText());
cmd = "C:\\Program Files\\SSHFS-Win\\bin\\sshfs.exe";
#elif defined AMNEZIA_DESKTOP
mountPath =
QString("%1/sftp:%2:%3").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), hostname, port);
QDir dir(mountPath);
if (!dir.exists()) {
dir.mkpath(mountPath);
}
cmd = "/usr/local/bin/sshfs";
#endif
#ifdef AMNEZIA_DESKTOP
QSharedPointer<QProcess> process;
process.reset(new QProcess());
m_sftpMountProcesses.append(process);
process->setProcessChannelMode(QProcess::MergedChannels);
connect(process.get(), &QProcess::readyRead, this, [this, process, mountPath]() {
QString s = process->readAll();
if (s.contains("The service sshfs has been started")) {
QDesktopServices::openUrl(QUrl("file:///" + mountPath));
}
qDebug() << s;
});
process->setProgram(cmd);
QString args = QString("%1@%2:/ %3 "
"-o port=%4 "
"-f "
"-o reconnect "
"-o rellinks "
"-o fstypename=SSHFS "
"-o ssh_command=/usr/bin/ssh.exe "
"-o UserKnownHostsFile=/dev/null "
"-o StrictHostKeyChecking=no "
"-o password_stdin")
.arg(username, hostname, mountPath, port);
// args.replace("\n", " ");
// args.replace("\r", " ");
// #ifndef Q_OS_WIN
// args.replace("reconnect-orellinks", "");
// #endif
process->setArguments(args.split(" ", Qt::SkipEmptyParts));
process->start();
process->waitForStarted(50);
if (process->state() != QProcess::Running) {
qDebug() << "onPushButtonSftpMountDriveClicked process not started";
qDebug() << args;
} else {
process->write((password + "\n").toUtf8());
}
// qDebug().noquote() << "onPushButtonSftpMountDriveClicked" << args;
#endif
}
bool InstallController::checkSshConnection()
{
ServerController serverController(m_settings);
ErrorCode errorCode = ErrorCode::NoError;
m_privateKeyPassphrase = "";
if (m_currentlyInstalledServerCredentials.secretData.contains("BEGIN")
&& m_currentlyInstalledServerCredentials.secretData.contains("PRIVATE KEY")) {
auto passphraseCallback = [this]() {
emit passphraseRequestStarted();
QEventLoop loop;
QObject::connect(this, &InstallController::passphraseRequestFinished, &loop, &QEventLoop::quit);
loop.exec();
return m_privateKeyPassphrase;
};
QString decryptedPrivateKey;
errorCode = serverController.getDecryptedPrivateKey(m_currentlyInstalledServerCredentials, decryptedPrivateKey,
passphraseCallback);
if (errorCode == ErrorCode::NoError) {
m_currentlyInstalledServerCredentials.secretData = decryptedPrivateKey;
} else {
emit installationErrorOccurred(errorString(errorCode));
return false;
}
}
QString output;
output = serverController.checkSshConnection(m_currentlyInstalledServerCredentials, &errorCode);
if (errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(errorString(errorCode));
return false;
} else {
if (output.contains(tr("Please login as the user"))) {
output.replace("\n", "");
emit installationErrorOccurred(output);
return false;
}
}
return true;
}
void InstallController::setEncryptedPassphrase(QString passphrase)
{
m_privateKeyPassphrase = passphrase;
emit passphraseRequestFinished();
}
void InstallController::addEmptyServer()
{
QJsonObject server;
server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName);
server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName);
server.insert(config_key::password, m_currentlyInstalledServerCredentials.secretData);
server.insert(config_key::port, m_currentlyInstalledServerCredentials.port);
server.insert(config_key::description, m_settings->nextAvailableServerName());
server.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None));
m_serversModel->addServer(server);
emit installServerFinished(tr("Server added successfully"));
}