#include "stdafx.h" #include "AddonManager.h" #include "SandMan.h" #include "OnlineUpdater.h" #include "../MiscHelpers/Common/Common.h" #include "../MiscHelpers/Common/OtherFunctions.h" #include #include #include #include "../QSbieAPI/Sandboxie/SbieTemplates.h" #include #include "../MiscHelpers/Archive/Archive.h" #include CAddonManager::CAddonManager(QObject* parent) : QObject(parent) { } void CAddonManager::UpdateAddonsWhenNotCached() { QVariantMap Data = theGUI->m_pUpdater->GetUpdateData(); if (!Data.isEmpty() && Data.contains("addons") && theGUI->m_pUpdater->GetLastUpdateTime() > QDateTime::currentDateTime().addDays(-1)) { OnUpdateData(Data, QVariantMap()); return; } else if (!m_Addons.isEmpty()) { QFileInfo info(theConf->GetConfigDir() + "/addons.json"); if (info.birthTime() > QDateTime::currentDateTime().addDays(-1)) return; } UpdateAddons(); } void CAddonManager::UpdateAddons() { theGUI->m_pUpdater->GetUpdates(this, SLOT(OnUpdateData(const QVariantMap&, const QVariantMap&))); } void CAddonManager::OnUpdateData(const QVariantMap& Data, const QVariantMap& Params) { if (Data.isEmpty() || Data["error"].toBool()) return; QVariantMap Addons = Data["addons"].toMap(); QJsonDocument doc(QJsonValue::fromVariant(Addons).toObject()); QFile::remove(theConf->GetConfigDir() + "/addons.json"); WriteStringToFile(theConf->GetConfigDir() + "/addons.json", doc.toJson()); LoadAddons(); emit DataUpdated(); } QList CAddonManager::GetAddons() { if (m_Addons.isEmpty()) { if (!LoadAddons()) UpdateAddons(); } else { foreach(const CAddonPtr& pAddon, m_Addons) pAddon->Installed = CheckAddon(pAddon); } return m_Addons; } bool CAddonManager::LoadAddons() { m_Addons.clear(); QString AddonPath = theConf->GetConfigDir() + "/addons.json"; QVariantMap Data = QJsonDocument::fromJson(ReadFileAsString(AddonPath).toUtf8()).toVariant().toMap(); foreach(const QVariant vAddon, Data["list"].toList()) { CAddonPtr pAddon = CAddonPtr(new CAddon(vAddon.toMap())); pAddon->Installed = CheckAddon(pAddon); m_Addons.append(pAddon); } return !m_Addons.isEmpty(); } CAddonPtr CAddonManager::GetAddon(const QString& Id) { if (m_Addons.isEmpty()) LoadAddons(); foreach(const CAddonPtr& pAddon, m_Addons) { if (pAddon->Id.compare(Id, Qt::CaseInsensitive) == 0) { pAddon->Installed = CheckAddon(pAddon); return pAddon; } } return CAddonPtr(); } bool CAddonManager::HasAddon(const QString& Id) { CAddonPtr pAddon = GetAddon(Id); return pAddon && pAddon->Installed; } bool CAddonManager::CheckAddon(const CAddonPtr& pAddon) { QString Key = pAddon->GetSpecificEntry("uninstall_key").toString(); if (!Key.isEmpty()) { QSettings settings(Key, QSettings::NativeFormat); QString Uninstall = settings.value("UninstallString").toString(); return !Uninstall.isEmpty(); } QStringList Files = pAddon->GetSpecificEntry("files").toStringList(); foreach(const QString & File, Files) { if (theGUI->GetCompat()->CheckFile(ExpandPath(File))) return true; } return false; } SB_PROGRESS CAddonManager::TryInstallAddon(const QString& Id, QWidget* pParent, const QString& Prompt) { if (QMessageBox("Sandboxie-Plus", Prompt.isEmpty() ? tr("Do you want to download and install %1?").arg(Id) : Prompt, QMessageBox::Question, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape, QMessageBox::NoButton, pParent ? pParent : theGUI).exec() != QMessageBox::Yes) return SB_ERR(SB_OtherError); SB_PROGRESS Status = InstallAddon(Id); if (Status.GetStatus() == OP_ASYNC) theGUI->AddAsyncOp(Status.GetValue(), false, tr("Installing: %1").arg(Id), pParent); else theGUI->CheckResults(QList() << Status, pParent); return Status; } SB_PROGRESS CAddonManager::InstallAddon(const QString& Id) { CAddonPtr pAddon = GetAddon(Id); if (!pAddon) return SB_ERR(SB_OtherError, QVariantList() << tr("Addon not found, please try updating the addon list in the global settings!")); if (pAddon->Installed) return SB_ERR(SB_OtherError, QVariantList() << tr("Addon already installed!")); QString Entry; QString Url = pAddon->GetSpecificEntry("download", &Entry).toString(); if (Url.isEmpty()) return SB_ERR(SB_OtherError, QVariantList() << tr("Addon has no download url, addon may not be available for your platform.")); QVariantMap Params; Params["name"] = Id; Params["path"] = theGUI->m_pUpdater->GetUpdateDir(true) + "/" + QUrl(Url).fileName(); Params["signature"] = pAddon->Data.value(Entry + "_sig"); theGUI->m_pUpdater->DownloadFile(Url, this, SLOT(OnAddonDownloaded(const QString&, const QVariantMap&)), Params); pAddon->pProgress = CSbieProgressPtr(new CSbieProgress()); connect(pAddon->pProgress.data(), SIGNAL(Finished()), this, SIGNAL(AddonInstalled())); pAddon->pProgress->ShowMessage(tr("Downloading Addon %1").arg(pAddon->Id)); return SB_PROGRESS(OP_ASYNC, pAddon->pProgress); } extern "C" NTSTATUS VerifyFileSignatureImpl(const wchar_t* FilePath, PVOID Signature, ULONG SignatureSize); void CAddonManager::OnAddonDownloaded(const QString& Path, const QVariantMap& Params) { CAddonPtr pAddon = GetAddon(Params["name"].toString()); QByteArray Signature = QByteArray::fromBase64(Params["signature"].toByteArray()); if (VerifyFileSignatureImpl(QString(Path).replace("/","\\").toStdWString().c_str(), Signature.data(), Signature.size()) < 0) { // !NT_SUCCESS pAddon->pProgress->Finish(SB_ERR(SB_OtherError, QVariantList() << tr("Download signature is not valid!"))); pAddon->pProgress.create(); return; } pAddon->pProgress->ShowMessage(tr("Installing Addon %1").arg(pAddon->Id)); QtConcurrent::run(CAddonManager::InstallAddonAsync, Path, pAddon); } void CAddonManager::InstallAddonAsync(const QString& FilePath, CAddonPtr pAddon) { SB_STATUS Status = SB_OK; CArchive Archive(FilePath); if (Archive.Open() == 1) { QString FileDir = Split2(FilePath, ".", true).first.replace("/", "\\"); if (Archive.Extract(FileDir)) { QString Cmd = pAddon->GetSpecificEntry("installer").toString(); QString Path = ExpandPath(pAddon->GetSpecificEntry("install_path").toString()); if (!Cmd.isEmpty() && QFile::exists(FileDir + Cmd)) { pAddon->pProgress->ShowMessage(tr("Running Installer for %1").arg(pAddon->Id)); std::wstring sbiehome = theAPI->GetSbiePath().toStdWString(); std::wstring plusdata = theConf->GetConfigDir().toStdWString(); LPWCH environmentStrings = GetEnvironmentStrings(); DWORD environmentLen = 0; for (LPWCH current = environmentStrings; *current; current += wcslen(current) + 1) environmentLen += wcslen(current) + 1; LPWCH modifiedEnvironment = (LPWCH)LocalAlloc(0, (environmentLen + sbiehome.length() + 1 + plusdata.length() + 1 + 1) * sizeof(wchar_t)); memcpy(modifiedEnvironment, environmentStrings, (environmentLen + 1) * sizeof(wchar_t)); FreeEnvironmentStrings(environmentStrings); LPWCH modifiedEnvironmentEnd = modifiedEnvironment + environmentLen; wcscpy(modifiedEnvironmentEnd, L"SBIEHOME="); wcscat(modifiedEnvironmentEnd, sbiehome.c_str()); modifiedEnvironmentEnd += wcslen(modifiedEnvironmentEnd) + 1; wcscpy(modifiedEnvironmentEnd, L"PLUSDATA="); wcscat(modifiedEnvironmentEnd, plusdata.c_str()); modifiedEnvironmentEnd += wcslen(modifiedEnvironmentEnd) + 1; *modifiedEnvironmentEnd = 0; STARTUPINFO si = { sizeof(si), 0 }; PROCESS_INFORMATION pi = { 0 }; if (CreateProcessW(NULL, (wchar_t*)(FileDir + Cmd).toStdWString().c_str(), NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, modifiedEnvironment, NULL, &si, &pi)) { while (WaitForSingleObject(pi.hProcess, 1000) == WAIT_TIMEOUT && !pAddon->pProgress->IsCanceled()); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else Status = SB_ERR(SB_OtherError, QVariantList() << tr("Failed to start installer (%1)!").arg(GetLastError())); LocalFree(modifiedEnvironment); } else if (!Path.isEmpty()) { pAddon->pProgress->ShowMessage(tr("Copying Files for %1").arg(pAddon->Id)); std::wstring from; foreach(const QString & file, ListDir(FileDir)) { QString File = QString(file).replace("/", "\\"); from.append((FileDir + "\\" + File).toStdWString()); from.append(L"\0", 1); } from.append(L"\0", 1); std::wstring to; to.append(Path.toStdWString()); to.append(L"\0", 1); SHFILEOPSTRUCT SHFileOp; memset(&SHFileOp, 0, sizeof(SHFILEOPSTRUCT)); SHFileOp.hwnd = NULL; SHFileOp.wFunc = FO_MOVE; SHFileOp.pFrom = from.c_str(); SHFileOp.pTo = to.c_str(); SHFileOp.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR; SHFileOperation(&SHFileOp); } QDir(FileDir).removeRecursively(); } else Status = SB_ERR(SB_OtherError, QVariantList() << tr("Failed to unpack addon!")); Archive.Close(); } QFile::remove(FilePath); if (!Status.IsError()) { pAddon->Installed = CheckAddon(pAddon); if (!pAddon->Installed) Status = SB_ERR(SB_OtherError, QVariantList() << tr("Addon Installation Failed!")); } pAddon->pProgress->Finish(Status); pAddon->pProgress.create(); } SB_PROGRESS CAddonManager::TryRemoveAddon(const QString& Id, QWidget* pParent) { if (QMessageBox("Sandboxie-Plus", tr("Do you want to remove %1?").arg(Id), QMessageBox::Question, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape, QMessageBox::NoButton, pParent ? pParent : theGUI).exec() != QMessageBox::Yes) return SB_ERR(SB_OtherError); SB_PROGRESS Status = RemoveAddon(Id); if (Status.GetStatus() == OP_ASYNC) theGUI->AddAsyncOp(Status.GetValue(), false, tr("Removing: %1").arg(Id), pParent); else theGUI->CheckResults(QList() << Status, pParent); return Status; } SB_PROGRESS CAddonManager::RemoveAddon(const QString& Id) { CAddonPtr pAddon = GetAddon(Id); if (!pAddon) return SB_ERR(SB_OtherError, QVariantList() << tr("Addon not found!")); pAddon->pProgress = CSbieProgressPtr(new CSbieProgress()); QtConcurrent::run(CAddonManager::RemoveAddonAsync, pAddon); return SB_PROGRESS(OP_ASYNC, pAddon->pProgress); } void CAddonManager::CleanupPath(const QString& Path) { StrPair PathName = Split2(Path, "\\", true); if (ListDir(PathName.first).isEmpty()) { QDir().rmdir(PathName.first); //qDebug() << "delete dir" << PathName.first; CleanupPath(PathName.first); } } void CAddonManager::RemoveAddonAsync(CAddonPtr pAddon) { SB_STATUS Status = SB_OK; QString Key = pAddon->GetSpecificEntry("uninstall_key").toString(); if (!Key.isEmpty()) { QSettings settings(Key, QSettings::NativeFormat); QString Cmd = settings.value("UninstallString").toString(); pAddon->pProgress->ShowMessage(tr("Running Uninstaller for %1").arg(pAddon->Id)); STARTUPINFO si = { sizeof(si), 0 }; PROCESS_INFORMATION pi = { 0 }; if (CreateProcessW(NULL, (wchar_t*)Cmd.toStdWString().c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { while (WaitForSingleObject(pi.hProcess, 1000) == WAIT_TIMEOUT && !pAddon->pProgress->IsCanceled()); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else Status = SB_ERR(SB_OtherError, QVariantList() << tr("Failed to start uninstaller!")); } else { QStringList Files = pAddon->GetSpecificEntry("files").toStringList(); //foreach(const QString & File, Files) { // pAddon->pProgress->ShowMessage(tr("Removing %1").arg(File)); // QString FilePath = ExpandPath(File); // QFile::remove(FilePath); // CleanupPath(FilePath); //} std::wstring from; foreach(const QString & File, Files) { QString FilePath = ExpandPath(File); if (QFile::exists(FilePath)) { from.append(FilePath.toStdWString()); from.append(L"\0", 1); } } from.append(L"\0", 1); SHFILEOPSTRUCT SHFileOp; memset(&SHFileOp, 0, sizeof(SHFILEOPSTRUCT)); SHFileOp.hwnd = NULL; SHFileOp.wFunc = FO_DELETE; SHFileOp.pFrom = from.c_str(); SHFileOp.pTo = NULL; SHFileOp.fFlags = FOF_NOCONFIRMATION; SHFileOperation(&SHFileOp); } if (!Status.IsError()) { pAddon->Installed = CheckAddon(pAddon); if (pAddon->Installed) Status = SB_ERR(SB_OtherError, QVariantList() << tr("Addon Removal Failed!")); } pAddon->pProgress->Finish(Status); pAddon->pProgress.create(); } QString CAddonManager::ExpandPath(QString Path) { Path.replace("%SbieHome%", theAPI->GetSbiePath(), Qt::CaseInsensitive); Path.replace("%PlusData%", theConf->GetConfigDir(), Qt::CaseInsensitive); return theGUI->GetCompat()->ExpandPath(Path); } QString GetArch() { SYSTEM_INFO systemInfo; GetSystemInfo(&systemInfo); switch (systemInfo.wProcessorArchitecture) { case PROCESSOR_ARCHITECTURE_INTEL: return "x86"; case PROCESSOR_ARCHITECTURE_AMD64: return "x64"; case PROCESSOR_ARCHITECTURE_ARM64: return "a64"; } return "???"; } QVariant CAddon::GetSpecificEntry(const QString& Name, QString* pName) { #ifdef _M_ARM64 QString arch = "a64"; #elif _WIN64 QString arch = "x64"; #else QString arch = "x86"; #endif // // First we check the qt cpecific entry for our version of qt and platform // QString qt = QString("qt%1_%2_%3_%4").arg(QT_VERSION_MAJOR).arg(QT_VERSION_MINOR).arg(QT_VERSION_PATCH).arg(arch); #ifdef _DEBUG qt.append("d"); #endif // _DEBUG if (Data.contains(Name + "_" + qt)) { if (pName) *pName = Name + "_" + qt; return Data[Name + "_" + qt]; } // // Second we check the actual architecture // QString match = Data["match_arch"].toString(); if (match != "agent") arch = GetArch(); if (Data.contains(Name + "_" + arch)) { if (pName) *pName = Name + "_" + arch; return Data[Name + "_" + arch]; } // // last we try the unsoecific entry // if (Data.contains(Name)) { if (pName) *pName = Name; return Data[Name]; } return QString(); } QString CAddon::GetLocalizedEntry(const QString& Name) { if (Data.contains(Name + "_" + theGUI->m_Language)) return Data[Name + "_" + theGUI->m_Language].toString(); QString LangAux = theGUI->m_Language; // Short version as fallback LangAux.truncate(LangAux.lastIndexOf('_')); if (Data.contains(Name + "_" + LangAux)) return Data[Name + "_" + LangAux].toString(); return Data[Name].toString(); }