diff --git a/CHANGELOG.md b/CHANGELOG.md index bafb1256..0fdd0652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [1.10.2 / 5.65.2] - 2023-07-?? + +### Added +- re-added option to suspend sandboxed processes [#3126](https://github.com/sandboxie-plus/Sandboxie/issues/3126) + ### Changed - changed format of the addon data [#3135](https://github.com/sandboxie-plus/Sandboxie/issues/3135) - all users coming from versions 1.10.0 and 1.10.1 will need to reinstall the components in the addon manager diff --git a/SandboxiePlus/QSbieAPI/Sandboxie/BoxedProcess.cpp b/SandboxiePlus/QSbieAPI/Sandboxie/BoxedProcess.cpp index 1daec752..fb6e1b0c 100644 --- a/SandboxiePlus/QSbieAPI/Sandboxie/BoxedProcess.cpp +++ b/SandboxiePlus/QSbieAPI/Sandboxie/BoxedProcess.cpp @@ -54,7 +54,7 @@ CBoxedProcess::CBoxedProcess(quint32 ProcessId, class CSandBox* pBox) m_ReturnCode = STATUS_PENDING; m_uTerminated = 0; - //m_bSuspended = IsSuspended(); + m_bSuspended = false; m_bIsWoW64 = false; } @@ -280,6 +280,8 @@ void CBoxedProcess::InitProcessInfoImpl(void* ProcessHandle) { m_WorkingDir = CBoxedProcess__GetPebString(ProcessHandle, PhpoCurrentDirectory); } + + m_bSuspended = IsSuspended(); } bool CBoxedProcess::InitProcessInfoEx() @@ -291,14 +293,20 @@ bool CBoxedProcess::InitProcessInfoEx() return true; } -//extern "C" -//{ -// NTSYSCALLAPI NTSTATUS NTAPI NtTerminateProcess(_In_opt_ HANDLE ProcessHandle, _In_ NTSTATUS ExitStatus); -// NTSYSCALLAPI NTSTATUS NTAPI NtSuspendProcess(_In_ HANDLE ProcessHandle); -// NTSYSCALLAPI NTSTATUS NTAPI NtResumeProcess(_In_ HANDLE ProcessHandle); -//} +extern "C" +{ +NTSYSCALLAPI NTSTATUS NTAPI NtTerminateProcess(_In_opt_ HANDLE ProcessHandle, _In_ NTSTATUS ExitStatus); +NTSYSCALLAPI NTSTATUS NTAPI NtSuspendProcess(_In_ HANDLE ProcessHandle); +NTSYSCALLAPI NTSTATUS NTAPI NtResumeProcess(_In_ HANDLE ProcessHandle); -#include +NTSYSCALLAPI NTSTATUS NTAPI NtGetNextThread(HANDLE ProcessHandle, HANDLE ThreadHandle, ACCESS_MASK DesiredAccess, ULONG HandleAttributes, ULONG Flags, PHANDLE NewThreadHandle); + +#define OBJ_KERNEL_EXCLUSIVE 0x00010000L +#define OBJ_VALID_PRIVATE_ATTRIBUTES 0x00010000L +#define OBJ_ALL_VALID_ATTRIBUTES (OBJ_VALID_PRIVATE_ATTRIBUTES | OBJ_VALID_ATTRIBUTES) +} + +//#include SB_STATUS CBoxedProcess::Terminate() { @@ -327,7 +335,7 @@ bool CBoxedProcess::IsTerminated(quint64 forMs) const return ::GetTickCount64() - m_uTerminated > forMs; } -/*SB_STATUS CBoxedProcess::SetSuspend(bool bSet) +SB_STATUS CBoxedProcess::SetSuspend(bool bSet) { HANDLE ProcessHandle = OpenProcess(PROCESS_SUSPEND_RESUME, FALSE, (DWORD)m_ProcessId); if (ProcessHandle != INVALID_HANDLE_VALUE) @@ -351,43 +359,26 @@ bool CBoxedProcess::IsSuspended() const { bool isSuspended = true; - // todo: do that globally once per sec for all boxed processes - - // Note: If the specified process is a 64-bit process and the caller is a 32-bit process, this function fails and the last error code is ERROR_PARTIAL_COPY (299). - HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - if (hThreadSnap == INVALID_HANDLE_VALUE) - return false; - - THREADENTRY32 te32 = { 0 }; - te32.dwSize = sizeof(THREADENTRY32); - if (Thread32First(hThreadSnap, &te32)) + for(HANDLE hThread = NULL;;) { - do - { - if (te32.th32OwnerProcessID != m_ProcessId) - continue; - - HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION, FALSE, te32.th32ThreadID); + HANDLE nNextThread = NULL; + NTSTATUS status = NtGetNextThread(m->Handle, hThread, THREAD_QUERY_INFORMATION, 0, 0, &nNextThread); + if(hThread) NtClose(hThread); + if (!NT_SUCCESS(status)) + break; + hThread = nNextThread; - ULONG SuspendCount = 0; - NTSTATUS status = NtQueryInformationThread(hThread, (THREADINFOCLASS)35/ThreadSuspendCount/, &SuspendCount, sizeof(ULONG), NULL); - - CloseHandle(hThread); - - if (SuspendCount == 0) - { - isSuspended = false; - break; - } - - } while (Thread32Next(hThreadSnap, &te32)); - } - - CloseHandle(hThreadSnap); + ULONG SuspendCount = 0; + status = NtQueryInformationThread(hThread, (THREADINFOCLASS)35/*ThreadSuspendCount*/, &SuspendCount, sizeof(ULONG), NULL); + if (SuspendCount == 0) { + isSuspended = false; + NtClose(hThread); + break; + } + } return isSuspended; } -*/ void CBoxedProcess::ResolveSymbols(const QVector& Addresses) { diff --git a/SandboxiePlus/QSbieAPI/Sandboxie/BoxedProcess.h b/SandboxiePlus/QSbieAPI/Sandboxie/BoxedProcess.h index 9958645a..24c9c785 100644 --- a/SandboxiePlus/QSbieAPI/Sandboxie/BoxedProcess.h +++ b/SandboxiePlus/QSbieAPI/Sandboxie/BoxedProcess.h @@ -47,8 +47,8 @@ public: virtual bool IsTerminated(quint64 forMs = 0) const; virtual void SetTerminated(); - //virtual SB_STATUS SetSuspend(bool bSet); - //virtual bool IsSuspended() const; + virtual SB_STATUS SetSuspend(bool bSet); + virtual bool IsSuspended() const; virtual bool IsWoW64() const { return m_bIsWoW64; } @@ -79,7 +79,7 @@ protected: QDateTime m_StartTime; quint32 m_ReturnCode; quint64 m_uTerminated; - //bool m_bSuspended; + bool m_bSuspended; bool m_bIsWoW64; class CSandBox* m_pBox; diff --git a/SandboxiePlus/SandMan/AddonManager.cpp b/SandboxiePlus/SandMan/AddonManager.cpp index beae657e..9193c095 100644 --- a/SandboxiePlus/SandMan/AddonManager.cpp +++ b/SandboxiePlus/SandMan/AddonManager.cpp @@ -9,7 +9,7 @@ #include #include "../QSbieAPI/Sandboxie/SbieTemplates.h" #include -#include "../MiscHelpers/Archive/Archive.h" +#include "../../SandboxieTools/UpdUtil/UpdUtil.h" #include @@ -27,8 +27,8 @@ void CAddonManager::UpdateAddonsWhenNotCached() OnUpdateData(Data, QVariantMap()); return; } - else if (!m_Addons.isEmpty()) { - QFileInfo info(theConf->GetConfigDir() + "/addons.json"); + else if (!m_KnownAddons.isEmpty()) { + QFileInfo info(theConf->GetConfigDir() + "/" ADDONS_FILE); if (info.birthTime() > QDateTime::currentDateTime().addDays(-1)) return; } @@ -49,77 +49,111 @@ void CAddonManager::OnUpdateData(const QVariantMap& Data, const QVariantMap& Par QVariantMap Addons = Data["addons"].toMap(); QJsonDocument doc(QJsonValue::fromVariant(Addons).toObject()); - QFile::remove(theConf->GetConfigDir() + "/addons.json"); - WriteStringToFile(theConf->GetConfigDir() + "/addons.json", doc.toJson()); + QFile::remove(theConf->GetConfigDir() + "/" ADDONS_FILE); + WriteStringToFile(theConf->GetConfigDir() + "/" ADDONS_FILE, doc.toJson()); LoadAddons(); emit DataUpdated(); } -QList CAddonManager::GetAddons() +QList CAddonManager::GetAddons() { - if (m_Addons.isEmpty()) { + if (m_KnownAddons.isEmpty()) { if (!LoadAddons()) UpdateAddons(); } - else { - foreach(const CAddonPtr& pAddon, m_Addons) - pAddon->Installed = CheckAddon(pAddon); + + QMap Addons; + + m_Installed.clear(); + QDir Dir(theAPI->GetSbiePath() + ADDONS_PATH); + foreach(const QFileInfo & Info, Dir.entryInfoList(QStringList() << "*.json", QDir::Files | QDir::Hidden | QDir::System)) { + QString AddonPath = theAPI->GetSbiePath() + ADDONS_PATH + Info.fileName(); + QString AddonStr = ReadFileAsString(AddonPath); + QVariantMap Data = QJsonDocument::fromJson(AddonStr.toUtf8()).toVariant().toMap(); + m_Installed.append(CAddonPtr(new CAddon(Data))); + Addons.insert(Data["id"].toString().toLower(), CAddonInfoPtr(new CAddonInfo(Data, true))); } - return m_Addons; + + foreach(const CAddonPtr& pAddon, m_KnownAddons) { + CAddonInfoPtr& pInfo = Addons[pAddon->Id.toLower()]; + if (pInfo.isNull()) { + bool Installed = false; + + QString Key = pAddon->GetSpecificEntry("uninstallKey").toString(); + if (!Key.isEmpty()) { + QSettings settings(Key, QSettings::NativeFormat); + QString Uninstall = settings.value("UninstallString").toString(); + if (!Uninstall.isEmpty()) { + Installed = true; + m_Installed.append(CAddonPtr(new CAddon(pAddon->Data))); + } + } + + pInfo = CAddonInfoPtr(new CAddonInfo(pAddon->Data, Installed)); + } + else if (pInfo->Data["version"] != pAddon->Data["version"]) + pInfo->UpdateVersion = pAddon->Data["version"].toString(); + } + + return Addons.values(); } bool CAddonManager::LoadAddons() { - m_Addons.clear(); + m_KnownAddons.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); - } + QString AddonPath = theConf->GetConfigDir() + "/" ADDONS_FILE; + QString AddonStr = ReadFileAsString(AddonPath); + QVariantMap Data = QJsonDocument::fromJson(AddonStr.toUtf8()).toVariant().toMap(); + foreach(const QVariant vAddon, Data["list"].toList()) + m_KnownAddons.append(CAddonPtr(new CAddon(vAddon.toMap()))); - return !m_Addons.isEmpty(); + return !m_KnownAddons.isEmpty(); } -CAddonPtr CAddonManager::GetAddon(const QString& Id) +CAddonPtr CAddonManager::GetAddon(const QString& Id, EState State) { - if (m_Addons.isEmpty()) - LoadAddons(); + if (State != eNotINstalled) + { + foreach(const CAddonPtr & pAddon, m_Installed) { + if (pAddon->Id.compare(Id, Qt::CaseInsensitive) == 0) + return pAddon; + } + } - foreach(const CAddonPtr& pAddon, m_Addons) { - if (pAddon->Id.compare(Id, Qt::CaseInsensitive) == 0) { - pAddon->Installed = CheckAddon(pAddon); - return pAddon; + if (State != eInstalled) + { + if (m_KnownAddons.isEmpty()) + LoadAddons(); + + foreach(const CAddonPtr & pAddon, m_KnownAddons) { + if (pAddon->Id.compare(Id, Qt::CaseInsensitive) == 0) + return pAddon; } } return CAddonPtr(); } -bool CAddonManager::HasAddon(const QString& Id) +/*bool CAddonManager::CheckAddon(const CAddonPtr& pAddon) { - CAddonPtr pAddon = GetAddon(Id); - return pAddon && pAddon->Installed; -} - -bool CAddonManager::CheckAddon(const CAddonPtr& pAddon) -{ - QString Key = pAddon->GetSpecificEntry("uninstall_key").toString(); + QString Key = pAddon->GetSpecificEntry("uninstallKey").toString(); if (!Key.isEmpty()) { QSettings settings(Key, QSettings::NativeFormat); QString Uninstall = settings.value("UninstallString").toString(); return !Uninstall.isEmpty(); } - QStringList Files = pAddon->GetSpecificEntry("files").toStringList(); + / *QStringList Files = pAddon->GetSpecificEntry("files").toStringList(); foreach(const QString & File, Files) { if (theGUI->GetCompat()->CheckFile(ExpandPath(File))) return true; } - return false; -} + return false;* / + + QString AddonFile = theAPI->GetSbiePath() + ADDONS_PATH + pAddon->Id + ".json"; + return QFile::exists(AddonFile); +}*/ SB_PROGRESS CAddonManager::TryInstallAddon(const QString& Id, QWidget* pParent, const QString& Prompt) { @@ -135,151 +169,6 @@ SB_PROGRESS CAddonManager::TryInstallAddon(const QString& Id, QWidget* 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), @@ -294,100 +183,92 @@ SB_PROGRESS CAddonManager::TryRemoveAddon(const QString& Id, QWidget* pParent) return Status; } -SB_PROGRESS CAddonManager::RemoveAddon(const QString& Id) +SB_PROGRESS CAddonManager::InstallAddon(const QString& Id) { - CAddonPtr pAddon = GetAddon(Id); + CAddonPtr pAddon = GetAddon(Id, eNotINstalled); if (!pAddon) - return SB_ERR(SB_OtherError, QVariantList() << tr("Addon not found!")); + return SB_ERR(SB_OtherError, QVariantList() << tr("Addon not found, please try updating the addon list in the global settings!")); + + QFile::remove(theGUI->m_pUpdater->GetUpdateDir(true) + "/" ADDONS_FILE); + QFile::copy(theConf->GetConfigDir() + "/" ADDONS_FILE, theGUI->m_pUpdater->GetUpdateDir(true) + "/" ADDONS_FILE); + + QStringList Params; + Params.append("modify"); + Params.append("add:" + pAddon->Id); + Params.append("/agent_arch:" + GetAppArch()); + Params.append("/framework:" + GetFramework()); + Params.append("/step:apply"); + Params.append("/embedded"); + Params.append("/temp:" + theGUI->m_pUpdater->GetUpdateDir().replace("/", "\\")); pAddon->pProgress = CSbieProgressPtr(new CSbieProgress()); - QtConcurrent::run(CAddonManager::RemoveAddonAsync, pAddon); + QtConcurrent::run(CAddonManager::RunUpdaterAsync, pAddon, Params); + //QTimer::singleShot(10, this, [=]() { CAddonManager::RunUpdaterAsync(pAddon, Params); }); return SB_PROGRESS(OP_ASYNC, pAddon->pProgress); } -void CAddonManager::CleanupPath(const QString& Path) +SB_PROGRESS CAddonManager::RemoveAddon(const QString& Id) { - StrPair PathName = Split2(Path, "\\", true); - if (ListDir(PathName.first).isEmpty()) + CAddonPtr pAddon = GetAddon(Id, eInstalled); + if (!pAddon) + return SB_ERR(SB_OtherError, QVariantList() << tr("Addon not found!")); + + QStringList Params; + Params.append("modify"); + Params.append("remove:" + pAddon->Id); + Params.append("/step:apply"); + Params.append("/embedded"); + + pAddon->pProgress = CSbieProgressPtr(new CSbieProgress()); + QtConcurrent::run(CAddonManager::RunUpdaterAsync, pAddon, Params); + //QTimer::singleShot(10, this, [=]() { CAddonManager::RunUpdaterAsync(pAddon, Params); }); + return SB_PROGRESS(OP_ASYNC, pAddon->pProgress); +} + +QString GetUpdErrorStr(int exitCode); + +QString GetUpdErrorStr2(int exitCode) +{ + switch (exitCode) { - QDir().rmdir(PathName.first); - //qDebug() << "delete dir" << PathName.first; - CleanupPath(PathName.first); + case ERROR_NO_ADDON: return CAddonManager::tr("Addon Not Found"); + case ERROR_NO_ADDON2: return CAddonManager::tr("Addon is not available for this paltform"); + case ERROR_BAD_ADDON: return CAddonManager::tr("Missing instalation instructions"); + case ERROR_BAD_ADDON2: return CAddonManager::tr("Executing addon setup failed"); + case ERROR_DELETE: return CAddonManager::tr("Failed to delete a file during addon removal"); + default: return GetUpdErrorStr(exitCode); } } -void CAddonManager::RemoveAddonAsync(CAddonPtr pAddon) +void CAddonManager::RunUpdaterAsync(CAddonPtr pAddon, const QStringList& Params) { - SB_STATUS Status = SB_OK; +#ifdef _DEBUG + CSbieResult Status = COnlineUpdater::RunUpdater(Params, false, true); +#else + CSbieResult Status = COnlineUpdater::RunUpdater(Params, true, true); +#endif - 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!")); - } + if(Status.IsError()) + pAddon->pProgress->Finish(SB_ERR(SB_OtherError, QVariantList() << tr("Updater failed to to perform plugin operation"))); + else if(Status.GetValue() < 0) + pAddon->pProgress->Finish(SB_ERR(SB_OtherError, QVariantList() << tr("Updater failed to to perform plugin operation, error: %1").arg(GetUpdErrorStr2(Status.GetValue())))); 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(); + pAddon->pProgress->Finish(SB_OK); + pAddon->pProgress.clear(); } -QString CAddonManager::ExpandPath(QString Path) +QString CAddonManager::GetAppArch() { - Path.replace("%SbieHome%", theAPI->GetSbiePath(), Qt::CaseInsensitive); - Path.replace("%PlusData%", theConf->GetConfigDir(), Qt::CaseInsensitive); - - return theGUI->GetCompat()->ExpandPath(Path); +#ifdef _M_ARM64 + return "a64"; +#elif _WIN64 + return "x64"; +#else + return "x86"; +#endif } -QString GetArch() +QString CAddonManager::GetSysArch() { SYSTEM_INFO systemInfo; GetSystemInfo(&systemInfo); @@ -397,42 +278,43 @@ QString GetArch() case PROCESSOR_ARCHITECTURE_AMD64: return "x64"; case PROCESSOR_ARCHITECTURE_ARM64: return "a64"; } - return "???"; + return GetAppArch(); // fallback +} + +QString CAddonManager::GetFramework() +{ + QString qt = QString("qt%1.%2.%3").arg(QT_VERSION_MAJOR).arg(QT_VERSION_MINOR).arg(QT_VERSION_PATCH); +#ifdef _DEBUG + qt.append("d"); +#endif // _DEBUG + qt.append("_" + GetAppArch()); + return qt; } 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); + QString qt = CAddonManager::GetFramework(); #ifdef _DEBUG qt.append("d"); #endif // _DEBUG - if (Data.contains(Name + "_" + qt)) { - if (pName) *pName = Name + "_" + qt; - return Data[Name + "_" + qt]; + 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]; + QString match = Data["matchArch"].toString(); + QString arch = match != "agent" ? CAddonManager::GetSysArch() : CAddonManager::GetAppArch(); + if (Data.contains(Name + "-" + arch)) { + if (pName) *pName = Name + "-" + arch; + return Data[Name + "-" + arch]; } // @@ -447,7 +329,7 @@ QVariant CAddon::GetSpecificEntry(const QString& Name, QString* pName) return QString(); } -QString CAddon::GetLocalizedEntry(const QString& Name) +QString CAddonInfo::GetLocalizedEntry(const QString& Name) { if (Data.contains(Name + "_" + theGUI->m_Language)) return Data[Name + "_" + theGUI->m_Language].toString(); @@ -458,4 +340,4 @@ QString CAddon::GetLocalizedEntry(const QString& Name) return Data[Name + "_" + LangAux].toString(); return Data[Name].toString(); -} +} \ No newline at end of file diff --git a/SandboxiePlus/SandMan/AddonManager.h b/SandboxiePlus/SandMan/AddonManager.h index b24d120c..8b332e6a 100644 --- a/SandboxiePlus/SandMan/AddonManager.h +++ b/SandboxiePlus/SandMan/AddonManager.h @@ -6,21 +6,37 @@ class CAddon : public QObject { public: - CAddon(const QVariantMap& Data) : Installed(false), Data(Data) + CAddon(const QVariantMap& Data) : Data(Data) { Id = Data["id"].toString(); } + QString Id; QVariantMap Data; - bool Installed; CSbieProgressPtr pProgress; QVariant GetSpecificEntry(const QString& Name, QString* pName = NULL); - QString GetLocalizedEntry(const QString& Name); }; typedef QSharedPointer CAddonPtr; +class CAddonInfo +{ +public: + CAddonInfo(const QVariantMap& data, bool installed) : Data(data), Installed(installed) { + Id = Data["id"].toString(); + }; + + QString Id; + QVariantMap Data; + bool Installed; + QString UpdateVersion; + + QString GetLocalizedEntry(const QString& Name); +}; + +typedef QSharedPointer CAddonInfoPtr; + class CAddonManager : public QObject { Q_OBJECT @@ -32,15 +48,24 @@ public: void UpdateAddonsWhenNotCached(); void UpdateAddons(); - QList GetAddons(); + QList GetAddons(); - CAddonPtr GetAddon(const QString& Id); - bool HasAddon(const QString& Id); + enum EState { + eAny = 0, + eInstalled, + eNotINstalled + }; + + CAddonPtr GetAddon(const QString& Id, EState State = eAny); SB_PROGRESS TryInstallAddon(const QString& Id, QWidget* pParent, const QString& Prompt = QString()); SB_PROGRESS InstallAddon(const QString& Id); SB_PROGRESS TryRemoveAddon(const QString& Id, QWidget* pParent); SB_PROGRESS RemoveAddon(const QString& Id); + + static QString GetAppArch(); + static QString GetSysArch(); + static QString GetFramework(); signals: void DataUpdated(); @@ -48,18 +73,12 @@ signals: private slots: void OnUpdateData(const QVariantMap& Data, const QVariantMap& Params); - void OnAddonDownloaded(const QString& Path, const QVariantMap& Params); protected: - static bool CheckAddon(const CAddonPtr& pAddon); - static void CleanupPath(const QString& Path); + static void RunUpdaterAsync(CAddonPtr pAddon, const QStringList& Params); - static void InstallAddonAsync(const QString& FilePath, CAddonPtr pAddon); - static void RemoveAddonAsync(CAddonPtr pAddon); - - static QString ExpandPath(QString Path); - - QList m_Addons; + QList m_Installed; + QList m_KnownAddons; }; diff --git a/SandboxiePlus/SandMan/Forms/SettingsWindow.ui b/SandboxiePlus/SandMan/Forms/SettingsWindow.ui index 36f946db..efcf15ca 100644 --- a/SandboxiePlus/SandMan/Forms/SettingsWindow.ui +++ b/SandboxiePlus/SandMan/Forms/SettingsWindow.ui @@ -1227,6 +1227,11 @@ Status + + + Version + + Description diff --git a/SandboxiePlus/SandMan/OnlineUpdater.cpp b/SandboxiePlus/SandMan/OnlineUpdater.cpp index f1236114..3b91aec2 100644 --- a/SandboxiePlus/SandMan/OnlineUpdater.cpp +++ b/SandboxiePlus/SandMan/OnlineUpdater.cpp @@ -676,7 +676,8 @@ bool COnlineUpdater::ApplyUpdate(bool bSilent) if (Scope == eFull) Params.append("/open:sandman.exe"); - if (RunUpdater(Params, bSilent, Scope != eFull)) { + SB_RESULT(int) status = RunUpdater(Params, bSilent, Scope != eFull); + if (!status.IsError()) { if(bSilent) theConf->DelValue("Updater/UpdateVersion"); if (Scope == eMeta) @@ -690,15 +691,15 @@ bool COnlineUpdater::ApplyUpdate(bool bSilent) return false; } -bool COnlineUpdater::RunUpdater(const QStringList& Params, bool bSilent, bool Wait) +SB_RESULT(int) COnlineUpdater::RunUpdater(const QStringList& Params, bool bSilent, bool Wait) { if (bSilent) { SB_RESULT(int) Result = theAPI->RunUpdateUtility(Params, 2, Wait); if (!Result.IsError()) - return true; + return Result; // else fallback to ShellExecuteEx if (theConf->GetBool("Options/UpdateNoFallback", false)) - return false; + return Result; } std::wstring wFile = QString(QApplication::applicationDirPath() + "/UpdUtil.exe").replace("/", "\\").toStdWString(); @@ -708,7 +709,10 @@ bool COnlineUpdater::RunUpdater(const QStringList& Params, bool bSilent, bool Wa wParams += L"\"" + Param.toStdWString() + L"\""; } - return RunElevated(wFile, wParams, Wait ? INFINITE : 0) == 0; + int ExitCode = RunElevated(wFile, wParams, Wait ? INFINITE : 0); + if (ExitCode == STATUS_PENDING && !Wait) + ExitCode = 0; + return CSbieResult(ExitCode); } void COnlineUpdater::DownloadFile(const QString& Url, QObject* receiver, const char* member, const QVariantMap& Params) diff --git a/SandboxiePlus/SandMan/OnlineUpdater.h b/SandboxiePlus/SandMan/OnlineUpdater.h index 78e65ea1..a9ca01cc 100644 --- a/SandboxiePlus/SandMan/OnlineUpdater.h +++ b/SandboxiePlus/SandMan/OnlineUpdater.h @@ -92,8 +92,10 @@ protected: bool AskDownload(const QVariantMap& Update); - bool RunUpdater(const QStringList& Params, bool bSilent, bool Wait = false); - bool RunInstaller2(const QString& FilePath, bool bSilent); + friend class CAddonManager; + + static SB_RESULT(int) RunUpdater(const QStringList& Params, bool bSilent, bool Wait = false); + static bool RunInstaller2(const QString& FilePath, bool bSilent); CNetworkAccessManager* m_RequestManager; CSbieProgressPtr m_pUpdateProgress; diff --git a/SandboxiePlus/SandMan/SandManRecovery.cpp b/SandboxiePlus/SandMan/SandManRecovery.cpp index 4afeaabc..950c2b36 100644 --- a/SandboxiePlus/SandMan/SandManRecovery.cpp +++ b/SandboxiePlus/SandMan/SandManRecovery.cpp @@ -76,8 +76,8 @@ QStringList CSandMan::GetFileCheckers(const CSandBoxPtr& pBox) { QStringList Checkers; - if (theGUI->GetAddonManager()->HasAddon("FileChecker")) - Checkers.append(pBox->Expand("powershell -exec bypass -nop -File \"%SbieHome%\\bin\\CheckFile.ps1\" -bin")); + if (!theGUI->GetAddonManager()->GetAddon("FileChecker", CAddonManager::eInstalled).isNull()) + Checkers.append(pBox->Expand("powershell -exec bypass -nop -File \"%SbieHome%\\addons\\FileChecker\\CheckFile.ps1\" -bin")); if (!pBox.isNull()) { foreach(const QString & Value, pBox->GetTextList("OnFileRecovery", true, false, true)) { diff --git a/SandboxiePlus/SandMan/Views/SbieView.cpp b/SandboxiePlus/SandMan/Views/SbieView.cpp index d832ce63..e41179e0 100644 --- a/SandboxiePlus/SandMan/Views/SbieView.cpp +++ b/SandboxiePlus/SandMan/Views/SbieView.cpp @@ -272,8 +272,8 @@ void CSbieView::CreateMenu() m_pMenuMarkLinger->setCheckable(true); m_pMenuMarkLeader = m_pMenuPreset->addAction(tr("Set Leader Process"), this, SLOT(OnProcessAction())); m_pMenuMarkLeader->setCheckable(true); - //m_pMenuSuspend = m_pMenuProcess->addAction(tr("Suspend"), this, SLOT(OnProcessAction())); - //m_pMenuResume = m_pMenuProcess->addAction(tr("Resume"), this, SLOT(OnProcessAction())); + m_pMenuSuspend = m_pMenuProcess->addAction(tr("Suspend"), this, SLOT(OnProcessAction())); + m_pMenuResume = m_pMenuProcess->addAction(tr("Resume"), this, SLOT(OnProcessAction())); } void CSbieView::CreateOldMenu() @@ -607,7 +607,7 @@ bool CSbieView::UpdateMenu(bool bAdvanced, const CSandBoxPtr &pBox, int iSandBox return bBoxBusy == false; } -void CSbieView::UpdateProcMenu(const CBoxedProcessPtr& pProcess, int iProcessCount) +void CSbieView::UpdateProcMenu(const CBoxedProcessPtr& pProcess, int iProcessCount, int iSuspendedCount) { m_pMenuLinkTo->setEnabled(iProcessCount == 1); @@ -640,8 +640,8 @@ void CSbieView::UpdateProcMenu(const CBoxedProcessPtr& pProcess, int iProcessCou m_pMenuMarkLeader->setChecked(pProcess.objectCast()->IsLeaderProgram()); } - //m_pMenuSuspend->setEnabled(iProcessCount > iSuspendedCount); - //m_pMenuResume->setEnabled(iSuspendedCount > 0); + m_pMenuSuspend->setEnabled(iProcessCount > iSuspendedCount); + m_pMenuResume->setEnabled(iSuspendedCount > 0); } bool CSbieView::UpdateMenu() @@ -655,7 +655,7 @@ bool CSbieView::UpdateMenu() int iProcessCount = 0; int iSandBoxeCount = 0; int iGroupe = 0; - //int iSuspendedCount = 0; + int iSuspendedCount = 0; QModelIndexList Rows = m_pSbieTree->selectedRows(); foreach(const QModelIndex& Index, Rows) { @@ -666,8 +666,8 @@ bool CSbieView::UpdateMenu() { m_CurProcesses.append(pProcess); iProcessCount++; - //if (pProcess->IsSuspended()) - // iSuspendedCount++; + if (pProcess->IsSuspended()) + iSuspendedCount++; } else { @@ -699,7 +699,7 @@ bool CSbieView::UpdateMenu() m_pDelGroupe->setVisible(iGroupe > 0 && iSandBoxeCount == 0 && iProcessCount == 0); if (!pProcess.isNull()) - UpdateProcMenu(pProcess, iProcessCount); + UpdateProcMenu(pProcess, iProcessCount, iSuspendedCount); return UpdateMenu(bAdvanced, pBox, iSandBoxeCount, bBoxBusy); } @@ -1596,10 +1596,10 @@ void CSbieView::OnProcessAction(QAction* Action, const QList& pProcess.objectCast()->SetLingeringProgram(m_pMenuMarkLinger->isChecked()); else if (Action == m_pMenuMarkLeader) pProcess.objectCast()->SetLeaderProgram(m_pMenuMarkLeader->isChecked()); - /*else if (Action == m_pMenuSuspend) + else if (Action == m_pMenuSuspend) Results.append(pProcess->SetSuspend(true)); else if (Action == m_pMenuResume) - Results.append(pProcess->SetSuspend(false));*/ + Results.append(pProcess->SetSuspend(false)); } theGUI->CheckResults(Results, this); diff --git a/SandboxiePlus/SandMan/Views/SbieView.h b/SandboxiePlus/SandMan/Views/SbieView.h index 4d803e37..fe57237d 100644 --- a/SandboxiePlus/SandMan/Views/SbieView.h +++ b/SandboxiePlus/SandMan/Views/SbieView.h @@ -124,7 +124,7 @@ private: void CreateTrayMenu(); bool UpdateMenu(bool bAdvanced, const CSandBoxPtr &pBox, int iSandBoxeCount = 1, bool bBoxBusy = false); - void UpdateProcMenu(const CBoxedProcessPtr &pProcess = CBoxedProcessPtr(), int iProcessCount = 0); + void UpdateProcMenu(const CBoxedProcessPtr &pProcess = CBoxedProcessPtr(), int iProcessCount = 0, int iSuspendedCount = 0); bool UpdateMenu(); void UpdateMoveMenu(); void RenameGroup(const QString OldName, const QString NewName); @@ -212,8 +212,8 @@ private: QAction* m_pMenuMarkLinger; QAction* m_pMenuMarkLeader; QAction* m_pMenuPinToRun; - //QAction* m_pMenuSuspend; - //QAction* m_pMenuResume; + QAction* m_pMenuSuspend; + QAction* m_pMenuResume; QAction* m_pRemove; diff --git a/SandboxiePlus/SandMan/Views/TraceView.cpp b/SandboxiePlus/SandMan/Views/TraceView.cpp index fea0e814..0d0d23fc 100644 --- a/SandboxiePlus/SandMan/Views/TraceView.cpp +++ b/SandboxiePlus/SandMan/Views/TraceView.cpp @@ -378,7 +378,7 @@ void CTraceView::SetEnabled(bool bSet) void CTraceView::OnShowStack() { - if (!theGUI->GetAddonManager()->HasAddon("DbgHelp")) + if (!theGUI->GetAddonManager()->GetAddon("DbgHelp", CAddonManager::eInstalled).isNull()) theGUI->GetAddonManager()->TryInstallAddon("DbgHelp", this, tr("To use the stack traces feature the DbgHelp.dll and SymSrv.dll are required, do you want to download and install them?")); theAPI->GetGlobalSettings()->SetBool("MonitorStackTrace", m_pShowStack->isChecked()); m_pTrace->m_pStackView->setVisible(m_pShowStack->isChecked()); diff --git a/SandboxiePlus/SandMan/Windows/SettingsWindow.cpp b/SandboxiePlus/SandMan/Windows/SettingsWindow.cpp index b72bde0c..71b43bc4 100644 --- a/SandboxiePlus/SandMan/Windows/SettingsWindow.cpp +++ b/SandboxiePlus/SandMan/Windows/SettingsWindow.cpp @@ -1536,15 +1536,21 @@ void CSettingsWindow::OnOptChanged() void CSettingsWindow::OnLoadAddon() { ui.treeAddons->clear(); - foreach(const CAddonPtr pAddon, theGUI->GetAddonManager()->GetAddons()) { + foreach(const CAddonInfoPtr pAddon, theGUI->GetAddonManager()->GetAddons()) { QTreeWidgetItem* pItem = new QTreeWidgetItem; pItem->setText(0, pAddon->GetLocalizedEntry("name")); if(!pAddon->Data["mandatory"].toBool()) pItem->setData(0, Qt::UserRole, pAddon->Id); pItem->setIcon(0, pAddon->Data.contains("icon") ? CSandMan::GetIcon(pAddon->Data["icon"].toString()) : CSandMan::GetIcon("Addon")); - pItem->setText(1, pAddon->Installed ? tr("Installed") : ""); - pItem->setText(2, pAddon->GetLocalizedEntry("description")); + if (pAddon->Installed) { + if(!pAddon->UpdateVersion.isEmpty()) + pItem->setText(1, tr("Update Available")); + else + pItem->setText(1, tr("Installed")); + } + pItem->setText(2, pAddon->Data["version"].toString()); + pItem->setText(3, pAddon->GetLocalizedEntry("description")); ui.treeAddons->addTopLevelItem(pItem); } diff --git a/SandboxiePlus/SandMan/Wizards/BoxAssistant.cpp b/SandboxiePlus/SandMan/Wizards/BoxAssistant.cpp index bb9c8010..b13aa1ba 100644 --- a/SandboxiePlus/SandMan/Wizards/BoxAssistant.cpp +++ b/SandboxiePlus/SandMan/Wizards/BoxAssistant.cpp @@ -96,7 +96,7 @@ void CBoxAssistant::OnToggleDebugger() { m_bUseDebugger = !m_bUseDebugger; - if (m_bUseDebugger && !theGUI->GetAddonManager()->HasAddon("V4dbg")) + if (m_bUseDebugger && theGUI->GetAddonManager()->GetAddon("V4dbg", CAddonManager::eInstalled).isNull()) theGUI->GetAddonManager()->TryInstallAddon("V4dbg", this, tr("To debug troubleshooting scripts you need the V4 Script Debugger addon, do you want to download and install it?")); QString title = windowTitle(); diff --git a/SandboxiePlus/version.h b/SandboxiePlus/version.h index a4ce4896..e67a51fe 100644 --- a/SandboxiePlus/version.h +++ b/SandboxiePlus/version.h @@ -2,8 +2,8 @@ #define VERSION_MJR 1 #define VERSION_MIN 10 -#define VERSION_REV 1 -#define VERSION_UPD 1 +#define VERSION_REV 2 +#define VERSION_UPD 0 #ifndef STR #define STR2(X) #X diff --git a/SandboxieTools/Common/json/JSONValue.cpp b/SandboxieTools/Common/json/JSONValue.cpp index a66cd3dc..ee6b5ab9 100644 --- a/SandboxieTools/Common/json/JSONValue.cpp +++ b/SandboxieTools/Common/json/JSONValue.cpp @@ -876,7 +876,7 @@ std::wstring JSONValue::StringifyString(const std::wstring &str) { wchar_t chr = *iter; - if (chr == L'"' || chr == L'\\' || chr == L'/') + if (chr == L'"' || chr == L'\\' /*|| chr == L'/'*/) { str_out += L'\\'; str_out += chr; diff --git a/SandboxieTools/Common/verify.c b/SandboxieTools/Common/verify.c index 4b1c0f52..82bddb28 100644 --- a/SandboxieTools/Common/verify.c +++ b/SandboxieTools/Common/verify.c @@ -279,7 +279,6 @@ NTSTATUS MyHashBuffer( ) { NTSTATUS status; - IO_STATUS_BLOCK iosb; MY_HASH_OBJ hashObj; if (!NT_SUCCESS(status = MyInitHash(&hashObj))) diff --git a/SandboxieTools/UpdUtil/UpdUtil.cpp b/SandboxieTools/UpdUtil/UpdUtil.cpp index 6864ea06..013537b8 100644 --- a/SandboxieTools/UpdUtil/UpdUtil.cpp +++ b/SandboxieTools/UpdUtil/UpdUtil.cpp @@ -60,12 +60,15 @@ struct SFile } State; }; -struct SFileMap +struct SFiles { - //SFileMap() : Status(eLoaded) {} - std::map> Map; std::wstring Sign; +}; + +struct SRelease: SFiles +{ + //SRelease() : Status(eLoaded) {} std::wstring Version; int iUpdate; std::wstring CI; @@ -75,17 +78,210 @@ struct SFileMap // eScanned, // ePrepared //} Status; + std::wstring AgentArch; + std::wstring Framework; }; typedef std::list TScope; +struct SAddon : SFiles +{ + std::wstring Id; -std::shared_ptr ScanDir(std::wstring Path) + /*std::wstring Name; + std::wstring Icon; + std::wstring Description; + std::wstring Version; + std::wstring InfoUrl;*/ + + JSONObject Data; + + bool IsDefault; + + std::wstring InstallPath; + std::wstring Installer; + std::wstring UninstallKey; +}; + +typedef std::map> TAddonMap; + + +std::wstring ReadRegistryStringValue(std::wstring key, const std::wstring& valueName) +{ + auto RootPath = Split2(key, L"\\"); + + HKEY hKey; + if (_wcsicmp(RootPath.first.c_str(), L"HKEY_LOCAL_MACHINE") == 0) + hKey = HKEY_LOCAL_MACHINE; + else if (_wcsicmp(RootPath.first.c_str(), L"HKEY_CURRENT_USER") == 0) + hKey = HKEY_CURRENT_USER; + else + return L""; + + if (RegOpenKeyEx(hKey, RootPath.second.c_str(), 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + wchar_t valueData[0x1000] = L""; + DWORD dataSize = sizeof(valueData); + DWORD valueType; + + RegQueryValueExW(hKey, valueName.c_str(), NULL, &valueType, (LPBYTE)valueData, &dataSize); + + RegCloseKey(hKey); + + return valueData; + } + return L""; +} + +std::wstring GetBinaryArch(const std::wstring& file) +{ + std::wstring arch; + + HANDLE hFile = CreateFile(file.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + goto finish; + + IMAGE_DOS_HEADER dosHeader; + DWORD bytesRead; + if (!ReadFile(hFile, &dosHeader, sizeof(IMAGE_DOS_HEADER), &bytesRead, NULL) || bytesRead != sizeof(IMAGE_DOS_HEADER)) + goto finish; + + if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE) + goto finish; + + SetFilePointer(hFile, dosHeader.e_lfanew, NULL, FILE_BEGIN); + IMAGE_NT_HEADERS ntHeader; + if (!ReadFile(hFile, &ntHeader, sizeof(IMAGE_NT_HEADERS), &bytesRead, NULL) || bytesRead != sizeof(IMAGE_NT_HEADERS)) + goto finish; + + if (ntHeader.Signature != IMAGE_NT_SIGNATURE) + goto finish; + + switch (ntHeader.FileHeader.Machine) + { + case IMAGE_FILE_MACHINE_I386: arch = L"x86"; break; + case IMAGE_FILE_MACHINE_AMD64: arch = L"x64"; break; + case IMAGE_FILE_MACHINE_ARM64: arch = L"a64"; break; + } + +finish: + if(hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); + + return arch; +} + +std::wstring GetFileVersion(const std::wstring& file) +{ + LPVOID versionInfo = NULL; + VS_FIXEDFILEINFO *fixedFileInfo = NULL; + UINT fixedFileInfoSize; + + DWORD versionHandle; + DWORD versionSize = GetFileVersionInfoSizeW(file.c_str(), &versionHandle); + if (versionSize == 0) + goto finish; + + versionInfo = malloc(versionSize); + + if (!GetFileVersionInfo(file.c_str(), versionHandle, versionSize, versionInfo)) + goto finish; + + if (!VerQueryValue(versionInfo, L"\\", (LPVOID *)&fixedFileInfo, &fixedFileInfoSize)) + goto finish; + +finish: + std::wstring version; + + if (fixedFileInfo) + { + DWORD fileVersionMS = fixedFileInfo->dwFileVersionMS; + DWORD fileVersionLS = fixedFileInfo->dwFileVersionLS; + + WORD majorVersion = HIWORD(fileVersionMS); + WORD minorVersion = LOWORD(fileVersionMS); + WORD buildNumber = HIWORD(fileVersionLS); + WORD revisionNumber = LOWORD(fileVersionLS); + + version = std::to_wstring(majorVersion) + L"." + std::to_wstring(minorVersion) + L"." + std::to_wstring(buildNumber); + if(revisionNumber) version += L"." + std::to_wstring(revisionNumber); + } + + if (versionInfo)free(versionInfo); + + return version; +} + +void CreateDirectoryTree(const std::wstring& root, const std::wstring& path) +{ + wchar_t* pathCopy = _wcsdup(path.c_str()); + wchar_t* token = _wcstok(pathCopy, L"\\"); + + wchar_t currentPath[MAX_PATH] = L""; + wcscpy(currentPath, root.c_str()); + + for (; token != NULL;) { + + wcscat(currentPath, L"\\"); + wcscat(currentPath, token); + + if (CreateDirectoryW(currentPath, NULL) == 0) { + if (GetLastError() != ERROR_ALREADY_EXISTS) { + printf("Error creating directory %S\n", currentPath); + } + } + + token = _wcstok(NULL, L"\\"); + } + + free(pathCopy); +} + +std::pair SplitName(std::wstring path) +{ + std::wstring name; + size_t pos = path.find_last_of(L"\\"); + if (pos == -1) { + name = path; + path.clear(); + } + else { + name = path.substr(pos + 1); + path.erase(pos); + path = L"\\" + path; + } + return std::make_pair(path, name); +} + +bool DeleteDirectoryRecursively(const std::wstring& root, const std::wstring& path, bool bWithFiles) +{ + std::vector Entries; + ListDir(root + path, Entries); + + for (int i = 0; i < Entries.size(); i++) + { + auto path_name = SplitName(Entries[i]); + if (path_name.second.empty()) { + if (!DeleteDirectoryRecursively(Entries[i], L"", bWithFiles)) + return false; + } + else if (bWithFiles) { + if (!DeleteFileW(Entries[i].c_str())) + return false; + } + else + return false; + } + + return !!RemoveDirectoryW((root + path).c_str()); +} + + +std::shared_ptr ScanDir(std::wstring Path) { if (Path.back() != L'\\') Path.push_back(L'\\'); - std::shared_ptr pFiles = std::make_shared(); + std::shared_ptr pFiles = std::make_shared(); std::vector Entries; Entries.push_back(Path); @@ -129,6 +325,14 @@ int GetJSONIntSafe(const JSONObject& root, const std::wstring& key, int def = 0) return (int)I->second->AsNumber(); } +int GetJSONBoolSafe(const JSONObject& root, const std::wstring& key, bool def = false) +{ + auto I = root.find(key); + if (I == root.end() || !I->second->IsBool()) + return def; + return I->second->AsBool(); +} + JSONObject GetJSONObjectSafe(const JSONObject& root, const std::wstring& key) { auto I = root.find(key); @@ -145,7 +349,7 @@ JSONArray GetJSONArraySafe(const JSONObject& root, const std::wstring& key) return I->second->AsArray(); } -std::string WriteUpdate(std::shared_ptr pFiles) +std::string WriteUpdate(std::shared_ptr pFiles) { JSONObject root; @@ -171,11 +375,14 @@ std::string WriteUpdate(std::shared_ptr pFiles) root[L"ci"] = new JSONValue(pFiles->CI); - //if(pFiles->Status == SFileMap::eScanned) + //if(pFiles->Status == SRelease::eScanned) // root[L"status"] = new JSONValue(L"scanned"); - //else if(pFiles->Status == SFileMap::ePrepared) + //else if(pFiles->Status == SRelease::ePrepared) // root[L"status"] = new JSONValue(L"prepared"); + root[L"framework"] = new JSONValue(pFiles->Framework); + root[L"agent_arch"] = new JSONValue(pFiles->AgentArch); + JSONValue *value = new JSONValue(root); auto wJson = value->Stringify(); delete value; @@ -183,14 +390,8 @@ std::string WriteUpdate(std::shared_ptr pFiles) return g_str_conv.to_bytes(wJson); } -std::shared_ptr ReadUpdate(const JSONObject& jsonObject) +void ReadFiles(const JSONArray& jsonFiles, std::shared_ptr pFiles) { - if (jsonObject.find(L"files") == jsonObject.end()) - return std::shared_ptr(); - - std::shared_ptr pFiles = std::make_shared(); - - JSONArray jsonFiles = GetJSONArraySafe(jsonObject, L"files"); for (auto I = jsonFiles.begin(); I != jsonFiles.end(); ++I) { if ((*I)->IsObject()) { JSONObject jsonFile = (*I)->AsObject(); @@ -207,6 +408,17 @@ std::shared_ptr ReadUpdate(const JSONObject& jsonObject) pFiles->Map[pFile->Path] = pFile; } } +} + +std::shared_ptr ReadUpdate(const JSONObject& jsonObject) +{ + if (jsonObject.find(L"files") == jsonObject.end()) + return std::shared_ptr(); + + std::shared_ptr pFiles = std::make_shared(); + + JSONArray jsonFiles = GetJSONArraySafe(jsonObject, L"files"); + ReadFiles(jsonFiles, pFiles); pFiles->Sign = GetJSONStringSafe(jsonObject, L"signature"); @@ -217,14 +429,17 @@ std::shared_ptr ReadUpdate(const JSONObject& jsonObject) std::wstring status = GetJSONStringSafe(jsonObject, L"status"); //if(status == L"scanned") - // pFiles->Status = SFileMap::eScanned; + // pFiles->Status = SRelease::eScanned; //else if(status == L"prepared") - // pFiles->Status = SFileMap::ePrepared; + // pFiles->Status = SRelease::ePrepared; + + pFiles->Framework = GetJSONStringSafe(jsonObject, L"framework"); + pFiles->AgentArch = GetJSONStringSafe(jsonObject, L"agent_arch"); return pFiles; } -bool VerifyUpdate(std::shared_ptr pFiles) +bool VerifyUpdate(std::shared_ptr pFiles) { bool pass = false; @@ -260,22 +475,6 @@ bool VerifyUpdate(std::shared_ptr pFiles) return pass; } -std::pair SplitName(std::wstring path) -{ - std::wstring name; - size_t pos = path.find_last_of(L"\\"); - if (pos == -1) { - name = path; - path.clear(); - } - else { - name = path.substr(pos + 1); - path.erase(pos); - path = L"\\" + path; - } - return std::make_pair(path, name); -} - void AddFilesToScope(std::shared_ptr pScope, const WCHAR* pFiles) { for (const WCHAR* pFile = pFiles; *pFile; pFile += wcslen(pFile) + 1) @@ -319,9 +518,9 @@ bool InScope(std::shared_ptr pScope, std::wstring name) return false; } -int FindChanges(std::shared_ptr pNewFiles, std::wstring base_dir, std::wstring temp_dir, std::shared_ptr pScope) +int FindChanges(std::shared_ptr pNewFiles, std::wstring base_dir, std::wstring temp_dir, std::shared_ptr pScope) { - std::shared_ptr pOldFiles = ScanDir(base_dir); + std::shared_ptr pOldFiles = ScanDir(base_dir); if (!pOldFiles) return ERROR_SCAN; @@ -354,7 +553,7 @@ BOOLEAN WebDownload(std::wstring url, PSTR* pData, ULONG* pDataLength) return WebDownload(domain.c_str(), path.c_str(), pData, pDataLength); } -int DownloadUpdate(std::wstring temp_dir, std::shared_ptr pNewFiles) +int DownloadUpdate(std::wstring temp_dir, std::shared_ptr pNewFiles) { std::wcout << L"Downloading" << std::endl; @@ -368,7 +567,7 @@ int DownloadUpdate(std::wstring temp_dir, std::shared_ptr pNewFiles) auto path_name = SplitName(I->second->Path); if (!path_name.first.empty()) - CreateDirectoryW((temp_dir + path_name.first).c_str(), NULL); + CreateDirectoryTree(temp_dir, path_name.first); ULONG hashSize; PVOID hash = NULL; @@ -421,35 +620,7 @@ int DownloadUpdate(std::wstring temp_dir, std::shared_ptr pNewFiles) return Count; } -int LoadUpdate(std::wstring temp_dir, std::shared_ptr& pNewFiles) -{ - char* aJson = NULL; - std::wstring file_path = temp_dir + L"\\" _T(UPDATE_FILE); - if (NT_SUCCESS(MyReadFile((wchar_t*)file_path.c_str(), 1024 * 1024, (PVOID*)&aJson, NULL)) && aJson != NULL) - { - JSONValue* jsonObject = JSON::Parse(aJson); - if (jsonObject) { - if (jsonObject->IsObject()) - pNewFiles = ReadUpdate(jsonObject->AsObject()); - delete jsonObject; - } - free(aJson); - } - - if(!pNewFiles){ - std::wcout << L"No pending update found!" << std::endl; - return ERROR_LOAD; - } - - if (!VerifyUpdate(pNewFiles)) { - std::wcout << L"INVALID " _T(UPDATE_FILE) L" SIGNATURE !!!" << std::endl; - return ERROR_SIGN; - } - - return 0; -} - -int ApplyUpdate(std::wstring base_dir, std::wstring temp_dir, std::shared_ptr pNewFiles) +int ApplyUpdate(std::wstring base_dir, std::wstring temp_dir, std::shared_ptr pNewFiles) { std::wcout << L"Applying updates" << std::endl; @@ -470,7 +641,7 @@ int ApplyUpdate(std::wstring base_dir, std::wstring temp_dir, std::shared_ptrsecond->Path; if (!path_name.first.empty()) - CreateDirectoryW((base_dir + path_name.first).c_str(), NULL); + CreateDirectoryTree(base_dir, path_name.first); if(MoveFileExW(src.c_str(), dest.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) std::wcout << L" done" << std::endl; @@ -485,10 +656,9 @@ int ApplyUpdate(std::wstring base_dir, std::wstring temp_dir, std::shared_ptr& pFiles, const std::wstring& step, const std::wstring& temp_dir, const std::wstring& base_dir, const std::wstring& scope) +int ProcessUpdate(std::shared_ptr& pFiles, const std::wstring& step, const std::wstring& temp_dir, const std::wstring& base_dir, const std::wstring& scope) { int ret = 0; if (!pFiles || pFiles->Map.empty()) return ERROR_INTERNAL; - //if (step.empty() || step == L"scan" || pFiles->Status < SFileMap::eScanned) + //if (step.empty() || step == L"scan" || pFiles->Status < SRelease::eScanned) if (step.empty() || step == L"scan" || step == L"prepare" || step == L"apply") { std::shared_ptr pScope; if (!scope.empty()) pScope = GetScope(scope); ret = FindChanges(pFiles, base_dir, temp_dir, pScope); - //pFiles->Status = SFileMap::eScanned; + //pFiles->Status = SRelease::eScanned; if (ret <= 0) return ret; // error or nothing todo if (step == L"scan") return ret; } - //if (step.empty() || step == L"prepare" || pFiles->Status < SFileMap::ePrepared) + //if (step.empty() || step == L"prepare" || pFiles->Status < SRelease::ePrepared) if (step.empty() || step == L"prepare" || step == L"apply") { ret = DownloadUpdate(temp_dir, pFiles); - //pFiles->Status = SFileMap::ePrepared; + //pFiles->Status = SRelease::ePrepared; if (ret <= 0) return ret; // error or nothing todo if (step == L"prepare") @@ -540,6 +707,311 @@ int ProcessUpdate(std::shared_ptr& pFiles, const std::wstring& step, c return ret; } +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// addon stuff +// + +std::wstring GetSpecificEntry(const JSONObject& root, const std::wstring& name, const std::wstring& core_arch, const std::wstring& agent_arch, const std::wstring& framework) +{ + if (!framework.empty() && root.find(name + L"-" + framework) != root.end()) + return name + L"-" + framework; + + std::wstring match = GetJSONStringSafe(root, L"matchArch"); + std::wstring arch = (match != L"agent") ? core_arch : agent_arch; + if (!arch.empty() && root.find(name + L"-" + arch) != root.end()) + return name + L"-" + arch; + + return name; +} + +std::wstring GetSpecificEntryValue(const JSONObject& root, const std::wstring& name, const std::wstring& core_arch, const std::wstring& agent_arch, const std::wstring& framework) +{ + std::wstring entry = GetSpecificEntry(root, name, core_arch, agent_arch, framework); + return GetJSONStringSafe(root, entry); +} + +std::shared_ptr ReadAddon(const JSONObject& addon, const std::wstring& core_arch, const std::wstring& agent_arch, const std::wstring& framework) +{ + std::shared_ptr pAddon = std::make_shared(); + pAddon->Id = GetJSONStringSafe(addon, L"id"); + + std::wstring entry = GetSpecificEntry(addon, L"files", core_arch, agent_arch, framework); + + JSONArray jsonFiles = GetJSONArraySafe(addon, entry); + ReadFiles(jsonFiles, pAddon); + + pAddon->Sign = GetJSONStringSafe(addon, entry + L"-sig"); + + /*pAddon->Name = GetJSONStringSafe(addon, L"name"); + pAddon->Icon = GetJSONStringSafe(addon, L"icon"); + pAddon->Description = GetJSONStringSafe(addon, L"description"); + pAddon->Version = GetJSONStringSafe(addon, L"version"); + pAddon->InfoUrl = GetJSONStringSafe(addon, L"info_url");*/ + + pAddon->IsDefault = GetJSONBoolSafe(addon, L"default"); + + pAddon->InstallPath = GetSpecificEntryValue(addon, L"installPath", core_arch, agent_arch, framework); + pAddon->Installer = GetSpecificEntryValue(addon, L"installer", core_arch, agent_arch, framework); + pAddon->UninstallKey = GetSpecificEntryValue(addon, L"uninstallPey", core_arch, agent_arch, framework); + + for (auto I = addon.begin(); I != addon.end(); ++I) { + if (I->first.find(L'-') != std::wstring::npos) + continue; // skip all entries containting "-" + pAddon->Data.insert(*I); + } + + return pAddon; +} + +JSONObject WriteAddon(std::shared_ptr pAddon) +{ + JSONObject addon; + + addon[L"id"] = new JSONValue(pAddon->Id); + + JSONArray files; + for (auto J = pAddon->Map.begin(); J != pAddon->Map.end(); ++J) + { + JSONObject file; + file[L"path"] = new JSONValue(J->second->Path); + file[L"hash"] = new JSONValue(J->second->Hash); + file[L"url"] = new JSONValue(J->second->Url); + files.push_back(new JSONValue(file)); + } + if (!files.empty()) { + addon[L"files"] = new JSONValue(files); + + addon[L"files-sig"] = new JSONValue(pAddon->Sign); + } + + /*addon[L"name"] = new JSONValue(pAddon->Name); + addon[L"icon"] = new JSONValue(pAddon->Icon); + addon[L"description"] = new JSONValue(pAddon->Description); + addon[L"version"] = new JSONValue(pAddon->Version); + addon[L"info_url"] = new JSONValue(pAddon->InfoUrl);*/ + + addon[L"default"] = new JSONValue(pAddon->IsDefault); + + if (!pAddon->InstallPath.empty()) addon[L"installPath"] = new JSONValue(pAddon->InstallPath); + if (!pAddon->Installer.empty()) addon[L"installer"] = new JSONValue(pAddon->Installer); + if (!pAddon->UninstallKey.empty()) addon[L"uninstallKey"] = new JSONValue(pAddon->UninstallKey); + + for (auto I = pAddon->Data.begin(); I != pAddon->Data.end(); ++I) { + addon.insert(*I); + } + + return addon; +} + +std::shared_ptr ReadAddons(const JSONObject& jsonObject, const std::wstring& core_arch, const std::wstring& agent_arch, const std::wstring& framework) +{ + if (jsonObject.find(L"list") == jsonObject.end()) + return std::shared_ptr(); + + std::shared_ptr pAddons = std::make_shared(); + + JSONArray list = GetJSONArraySafe(jsonObject, L"list"); + for (auto I = list.begin(); I != list.end(); ++I) { + if ((*I)->IsObject()) { + JSONObject addon = (*I)->AsObject(); + + std::shared_ptr pAddon = ReadAddon(addon, core_arch, agent_arch, framework); + + (*pAddons)[pAddon->Id] = pAddon; + } + } + + return pAddons; +} + +std::string WriteAddons(std::shared_ptr pAddons) +{ + JSONObject root; + + JSONArray list; + for (auto I = pAddons->begin(); I != pAddons->end(); ++I) + { + list.push_back(new JSONValue(WriteAddon(I->second))); + } + root[L"list"] = new JSONValue(list); + + JSONValue *value = new JSONValue(root); + auto wJson = value->Stringify(); + delete value; + + return g_str_conv.to_bytes(wJson); +} + +bool VerifyAddons(std::shared_ptr pAddons) +{ + for (auto I = pAddons->begin(); I != pAddons->end(); ++I) + { + if (I->second->Map.empty()) + continue; + if (!VerifyUpdate(I->second)) + return false; + } + return true; +} + +int DownloadAddon(std::shared_ptr pAddon, const std::wstring& step, const std::wstring& temp_dir, const std::wstring& base_dir) +{ + int ret = 0; + + if (!pAddon || pAddon->Map.empty()) { + std::wcout << L"Addon is not available for this paltform" << std::endl; + return ERROR_NO_ADDON2; + } + + // always mark all files for download + for (auto I = pAddon->Map.begin(); I != pAddon->Map.end(); ++I) + I->second->State = SFile::eChanged; + + //if (step.empty() || step == L"prepare" || pFiles->Status < SRelease::ePrepared) + if (step.empty() || step == L"prepare" || step == L"apply") + { + CreateDirectoryTree(temp_dir, pAddon->Id); + ret = DownloadUpdate(temp_dir + L"\\" + pAddon->Id, pAddon); + //pFiles->Status = SRelease::ePrepared; + if (ret <= 0) + return ret; // error or nothing todo + if (step == L"prepare") + return ret; + } + + return ret; +} + +int InstallAddon(std::shared_ptr pAddon, const std::wstring& temp_dir, const std::wstring& base_dir) +{ + int ret = 0; + + if (!pAddon->Installer.empty() && FileExists((temp_dir + L"\\" + pAddon->Id + pAddon->Installer).c_str())) { + + 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 + 32 + base_dir.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, base_dir.c_str()); + modifiedEnvironmentEnd += wcslen(modifiedEnvironmentEnd) + 1; + + *modifiedEnvironmentEnd = 0; + + STARTUPINFO si = { sizeof(si), 0 }; + PROCESS_INFORMATION pi = { 0 }; + std::wstring cmdLine = temp_dir + L"\\" + pAddon->Id + pAddon->Installer; + if (CreateProcessW(NULL, (wchar_t*)cmdLine.c_str(), NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, modifiedEnvironment, NULL, &si, &pi)) + { + while (WaitForSingleObject(pi.hProcess, 1000) == WAIT_TIMEOUT); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + else + ret = ERROR_BAD_ADDON2; + + LocalFree(modifiedEnvironment); + + if (ret >= 0 && !pAddon->UninstallKey.empty()) { + std::wstring cmd = ReadRegistryStringValue(pAddon->UninstallKey, L"UninstallString"); + if (cmd.empty()) // when the expected uninstall key is not present, + ret = ERROR_BAD_ADDON2; // it means the instalation failed + } + + return ret; + } + + if (pAddon->InstallPath.empty()) + return ERROR_BAD_ADDON; + + // install addon + CreateDirectoryTree(base_dir, pAddon->InstallPath); + ret = ApplyUpdate(base_dir + pAddon->InstallPath, temp_dir + L"\\" + pAddon->Id, pAddon); + + return ret; +} + +std::shared_ptr LoadAddon(const std::wstring& base_dir, const std::wstring& id) +{ + std::shared_ptr pAddon; + + char* aJson = NULL; + std::wstring file_path = base_dir + _T(ADDONS_PATH) + id + L".json"; + if (NT_SUCCESS(MyReadFile((wchar_t*)file_path.c_str(), 1024 * 1024, (PVOID*)&aJson, NULL)) && aJson != NULL) + { + JSONValue* jsonObject = JSON::Parse(aJson); + if (jsonObject) { + if (jsonObject->IsObject()) + pAddon = ReadAddon(jsonObject->AsObject(), L"", L"", L""); + delete jsonObject; + } + free(aJson); + } + + return pAddon; +} + +int RemoveAddon(std::shared_ptr pAddon, const std::wstring& base_dir) +{ + int ret = 0; + + if (!pAddon->UninstallKey.empty()) + { + std::wstring cmdLine = ReadRegistryStringValue(pAddon->UninstallKey, L"UninstallString"); + if(cmdLine.empty()) // when the expected uninstall key is not present, + return ret; // then seams addon was already uninstalled + + STARTUPINFO si = { sizeof(si), 0 }; + PROCESS_INFORMATION pi = { 0 }; + if (CreateProcessW(NULL, (wchar_t*)cmdLine.c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) + { + while (WaitForSingleObject(pi.hProcess, 1000) == WAIT_TIMEOUT); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + else + ret = ERROR_BAD_ADDON2; + + if (ret >= 0) { + std::wstring cmd = ReadRegistryStringValue(pAddon->UninstallKey, L"UninstallString"); + if (!cmd.empty()) // when the expected uninstall key is still present, + ret = ERROR_BAD_ADDON2; // it means the uninstalation failed + } + + return ret; + } + + if (pAddon->InstallPath.empty()) + return ERROR_BAD_ADDON; + + for (auto I = pAddon->Map.begin(); I != pAddon->Map.end(); ++I) + { + std::wstring file = base_dir + pAddon->InstallPath + I->second->Path; + if (!DeleteFileW(file.c_str()) && FileExists(file.c_str())) { + ret = ERROR_DELETE; + break; + } + } + + if (ret >= 0 && pAddon->InstallPath != L"\\") + DeleteDirectoryRecursively(base_dir, pAddon->InstallPath, false); + + return ret; +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// other stuff +// + int DownloadFile(std::wstring url, std::wstring file_path) { char* pData = NULL; @@ -602,16 +1074,14 @@ void PrintUsage() std::wcout << L"" << std::endl; } - - bool HasFlag(const std::vector& arguments, std::wstring name) { return std::find(arguments.begin(), arguments.end(), L"/" + name) != arguments.end(); } -std::wstring GetArgument(const std::vector& arguments, std::wstring name) +std::wstring GetArgument(const std::vector& arguments, std::wstring name, std::wstring mod = L"/") { - std::wstring prefix = L"/" + name + L":"; + std::wstring prefix = mod + name + L":"; for (size_t i = 0; i < arguments.size(); i++) { if (_wcsicmp(arguments[i].substr(0, prefix.length()).c_str(), prefix.c_str()) == 0) { return arguments[i].substr(prefix.length()); @@ -632,6 +1102,8 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, *wcsrchr(szPath, L'\\') = L'\0'; std::wstring wPath = szPath; + // install sandboxie-plus /version:1.6.1 /path:C:\Projects\Sandboxie\SandboxieLive\x64\Debug\Sandboxie_test + int nArgs = 0; LPWSTR* szArglist = CommandLineToArgvW(lpCmdLine, &nArgs); // GetCommandLineW() std::vector arguments; @@ -729,40 +1201,96 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, } return 0; } - else if (arguments.size() >= 2 && (arguments[0] == L"update" || arguments[0] == L"upgrade" || arguments[0] == L"install")) + else if ((arguments.size() >= 2 && (arguments[0] == L"update" || arguments[0] == L"upgrade" || arguments[0] == L"install")) || (arguments.size() >= 1 && arguments[0] == L"modify")) { - std::wstring software = arguments[1]; - - int ret; + int ret = 0; - std::shared_ptr pFiles; + bool bModify = arguments[0] == L"modify"; + bool bDoAddons = (bModify || arguments[0] == L"install"); + + std::wstring software = bModify ? L"sandboxie-addons" : arguments[1]; + + std::shared_ptr pFiles; + JSONValue* jsonAddons = NULL; + + auto add_addons = SplitStr(GetArgument(arguments, L"add", L""), L",", false); + auto remove_addons = SplitStr(GetArgument(arguments, L"remove", L""), L",", false); std::wstring step = GetArgument(arguments, L"step"); - if (!step.empty() && step != L"get") - { - ret = LoadUpdate(temp_dir, pFiles); - if (ret < 0) - return ret; - } + // + // Prepare update data, load from file or download + // bool bSave = false; - if(!pFiles || pFiles->Map.empty()) + if (!step.empty() && step != L"get") // load from file + { + if (!bModify) { + + char* aJson = NULL; + std::wstring file_path = temp_dir + L"\\" _T(UPDATE_FILE); + if (NT_SUCCESS(MyReadFile((wchar_t*)file_path.c_str(), 1024 * 1024, (PVOID*)&aJson, NULL)) && aJson != NULL) + { + JSONValue* jsonObject = JSON::Parse(aJson); + if (jsonObject) { + if (jsonObject->IsObject()) + pFiles = ReadUpdate(jsonObject->AsObject()); + delete jsonObject; + } + free(aJson); + } + + if (!pFiles) { + std::wcout << L"No pending update found!" << std::endl; + return ERROR_LOAD; + } + } + + if ((bModify && !add_addons.empty()) || arguments[0] == L"install") { + + char* aJson = NULL; + std::wstring file_path = temp_dir + L"\\" _T(ADDONS_FILE); + if (NT_SUCCESS(MyReadFile((wchar_t*)file_path.c_str(), 1024 * 1024, (PVOID*)&aJson, NULL)) && aJson != NULL) + { + JSONValue* jsonObject = JSON::Parse(aJson); + if (jsonObject) { + if (jsonObject->IsObject()) + jsonAddons = jsonObject; + } + free(aJson); + } + + if (!jsonAddons && arguments[0] != L"install") { // only fail here when we want to add addons + std::wcout << L"No addons found!" << std::endl; + return ERROR_LOAD; + } + } + } + else // load from server { std::wstringstream params; params << L"&action=" << arguments[0]; - std::wstring channel = GetArgument(arguments, L"channel"); - if (channel.empty()) channel = GetArgument(arguments, L"release"); - if (!channel.empty()) - params << L"&channel=" << channel; + std::wstring channel; + std::wstring version; + if (!bModify) + { + channel = GetArgument(arguments, L"channel"); + if (channel.empty()) channel = GetArgument(arguments, L"release"); + if (!channel.empty()) + params << L"&channel=" << channel; - std::wstring version = GetArgument(arguments, L"version"); - if (!version.empty()) { - params << L"&version=" << version; - std::wstring update = GetArgument(arguments, L"update"); - if (!update.empty()) - params << L"&update=" << update; + version = GetArgument(arguments, L"version"); + if (!version.empty()) { + params << L"&version=" << version; + std::wstring update = GetArgument(arguments, L"update"); + if (!update.empty()) + params << L"&update=" << update; + } + + // version or channel must be specified + if (version.empty() && channel.empty()) + return ERROR_INVALID; } params << L"&system=windows-" << g_osvi.dwMajorVersion << L"." << g_osvi.dwMinorVersion << L"." << g_osvi.dwBuildNumber << "-" << arch; @@ -776,10 +1304,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, std::wstring update_key = GetArgument(arguments, L"update_key"); if (!update_key.empty()) params << L"&update_key=" + update_key; - // version or channel must be specified - if (version.empty() && channel.empty()) - return ERROR_INVALID; - CreateDirectoryW(temp_dir.c_str(), NULL); char* aJson = NULL; @@ -789,26 +1313,35 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, JSONValue* jsonObject = JSON::Parse(aJson); if (jsonObject) { if (jsonObject->IsObject()) { - JSONObject update; - if (!version.empty() && channel.empty()) - update = GetJSONObjectSafe(jsonObject->AsObject(), L"update"); - else - update = GetJSONObjectSafe(jsonObject->AsObject(), L"release"); - pFiles = ReadUpdate(update); + + JSONObject root = jsonObject->AsObject(); + + if (!bModify) { + JSONObject update; + if (!version.empty() && channel.empty()) + update = GetJSONObjectSafe(root, L"update"); + else + update = GetJSONObjectSafe(root, L"release"); + pFiles = ReadUpdate(update); + } + + if (bDoAddons) { + auto I = root.find(L"addons"); + if (I != root.end() && I->second->IsObject()) { + jsonAddons = I->second; + root.erase(I); + } + } + } delete jsonObject; } free(aJson); - if (!pFiles) { + if (!bModify && !pFiles) { std::wcout << L"No update found !!!" << std::endl; return ERROR_GET; } - - if (!VerifyUpdate(pFiles)) { - std::wcout << L"INVALID " _T(UPDATE_FILE) L" SIGNATURE !!!" << std::endl; - return ERROR_SIGN; - } } ret = 0; @@ -816,7 +1349,32 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, bSave = true; } - if (step != L"get") + if (pFiles && !VerifyUpdate(pFiles)) { + std::wcout << L"INVALID Update SIGNATURE in " _T(UPDATE_FILE) "!!!" << std::endl; + return ERROR_SIGN; + } + + // + // when needed shutdown the software + // + + bool bRestart = false; + if (ret >= 0 && (step.empty() || step == L"apply")) + bRestart = HasFlag(arguments, L"restart"); + + if (bRestart) { + Execute(base_dir + L"\\KmdUtil.exe", L"scandll_silent"); + Execute(base_dir + L"\\KmdUtil.exe", L"stop SbieSvc"); + Execute(base_dir + L"\\KmdUtil.exe", L"stop SbieDrv"); + Sleep(3000); + Execute(base_dir + L"\\KmdUtil.exe", L"stop SbieDrv"); + } + + // + // apply update + // + + if (step != L"get" && !bModify) { std::wstring scope = GetArgument(arguments, L"scope"); //if(software == L"sandboxie" && scope.empty()) @@ -826,43 +1384,193 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, if (ret >= 0 && (step.empty() || step == L"apply")) { - bool bRestart = HasFlag(arguments, L"restart"); - - if (bRestart) { - Execute(base_dir + L"\\KmdUtil.exe", L"scandll_silent"); - Execute(base_dir + L"\\KmdUtil.exe", L"stop SbieSvc"); - - if (Execute(base_dir + L"\\KmdUtil.exe", L"stop SbieDrv")) - { - Sleep(3000); - Execute(base_dir + L"\\KmdUtil.exe", L"stop SbieDrv"); - } - } - ret = ApplyUpdate(base_dir, temp_dir, pFiles); if (ret <= 0) return ret; // error or nothing todo - - if (bRestart) { - Sleep(1000); - Execute(base_dir + L"\\KmdUtil.exe", L"start SbieSvc"); - } - - std::wstring wOpen = GetArgument(arguments, L"open"); - if (!wOpen.empty()) { - Execute(base_dir + L"\\start.exe", L"open_agent:" + wOpen); - } } } - if (ret >= 0) { - std::wstring file_path = temp_dir + L"\\" _T(UPDATE_FILE); - if (step.empty() || step == L"apply" || (ret == 0 && step != L"get")) - DeleteFileW(file_path.c_str()); // cleanup after apply or if there are no updates - //else { // store partial state to file - else if(bSave) { - std::string Json = WriteUpdate(pFiles); - MyWriteFile((wchar_t*)file_path.c_str(), (char*)Json.c_str(), Json.length()); + // + // load addons apropriate for the current instalation + // + + std::shared_ptr pAddons; + if (jsonAddons) + { + std::wstring core_arch = GetBinaryArch(base_dir + L"\\sbiesvc.exe"); + if (core_arch.empty()) { + // convert to new format + if (arch == L"x86_64") + core_arch = L"x64"; + else if (arch == L"i386") + core_arch = L"x86"; + else if (arch == L"arm64") + core_arch = L"a64"; + } + + std::wstring agent_arch = GetArgument(arguments, L"agent_arch"); + if (agent_arch.empty()) { + agent_arch = GetBinaryArch(base_dir + L"\\SandMan.exe"); + if (agent_arch.empty()) agent_arch = GetBinaryArch(base_dir + L"\\SbieCtrl.exe"); + } + + std::wstring framework = GetArgument(arguments, L"framework"); + if (framework.empty()) { + framework = GetFileVersion(base_dir + L"\\Qt5Core.dll"); + if (framework.empty()) framework = GetFileVersion(base_dir + L"\\Qt6Core.dll"); + if (!framework.empty()) framework = L"qt" + framework + L"_" + agent_arch; + } + + if (pFiles) { + if (agent_arch.empty()) agent_arch = pFiles->AgentArch; + if (framework.empty()) agent_arch = pFiles->Framework; + } + + pAddons = ReadAddons(jsonAddons->AsObject(), core_arch, agent_arch, framework); + + if(!pAddons){ + std::wcout << L"No addons found!" << std::endl; + if(bModify) + return ERROR_LOAD; + } + else if (!VerifyAddons(pAddons)) { + pAddons.reset(); // clear untrusted addon data + std::wcout << L"INVALID " _T(ADDONS_FILE) L" SIGNATURE !!!" << std::endl; + if(bModify) + return ERROR_SIGN; + } + } + + // + // install addons + // + + if (step != L"get" && pAddons) + { + if (!bModify) { // install case + + for (auto I = pAddons->begin(); I != pAddons->end(); ++I) + { + if (!I->second->IsDefault) + continue; + + // dont add default addons marked to be removed + auto F = std::find(remove_addons.begin(), remove_addons.end(), I->first); + if (F != remove_addons.end()) + continue; + + // dont add already added addons + F = std::find(add_addons.begin(), add_addons.end(), I->first); + if (F != add_addons.end()) + continue; + + add_addons.push_back(I->first); + } + } + + for (auto I = add_addons.begin(); I != add_addons.end(); ++I) + { + auto F = pAddons->find(*I); + if (F != pAddons->end()) { + std::wcout << L"Downloading addon " << *I << std::endl; + ret = DownloadAddon(F->second, step, temp_dir, base_dir); + } + else { + std::wcout << L"Addon Not Found" << std::endl; + ret = ERROR_NO_ADDON; + } + + if (ret >= 0 && (step.empty() || step == L"apply")) + { + std::wcout << L"Installing addon " << *I << std::endl; + ret = InstallAddon(F->second, temp_dir, base_dir); + + // register addon + if (ret >= 0) + { + JSONValue* value = new JSONValue(WriteAddon(F->second)); + auto wJson = value->Stringify(); + delete value; + + std::string aJson = g_str_conv.to_bytes(wJson); + MyWriteFile((wchar_t*)(base_dir + _T(ADDONS_PATH) + F->second->Id + L".json").c_str(), (char*)aJson.c_str(), aJson.length()); + + if (ret >= 0) + DeleteDirectoryRecursively(temp_dir + L"\\", F->second->Id + L"\\", true); + } + } + + if (ret < 0 && !bModify) // install case + ret = 0; // ignore addon errors + } + } + + // + // remove addons + // + + if(step != L"get" && !remove_addons.empty()) + { + for (auto I = remove_addons.begin(); I != remove_addons.end(); ++I) + { + std::shared_ptr pAddon = LoadAddon(base_dir, *I); + if (!pAddon) + ret = ERROR_NO_ADDON; + + if (ret >= 0 && (step.empty() || step == L"apply")) + { + std::wcout << L"Removing addon " << *I << std::endl; + ret = RemoveAddon(pAddon, base_dir); + + // unregister addon + if (ret >= 0) + DeleteFileW((base_dir + _T(ADDONS_PATH) + pAddon->Id + L".json").c_str()); + } + + if (ret < 0 && !bModify) // install case + ret = 0; // ignore addon errors + } + } + + // + // restart software + // + + if (bRestart) { + Sleep(1000); + Execute(base_dir + L"\\KmdUtil.exe", L"start SbieSvc"); + } + + std::wstring wOpen = GetArgument(arguments, L"open"); + if (!wOpen.empty()) { + Execute(base_dir + L"\\start.exe", L"open_agent:" + wOpen); + } + + // + // save data to file if needed + // + + if (ret >= 0) + { + if (!bModify) { + std::wstring file_path = temp_dir + L"\\" _T(UPDATE_FILE); + if (step.empty() || step == L"apply" || (ret == 0 && step != L"get")) + DeleteFileW(file_path.c_str()); // cleanup after apply or if there are no updates + //else { // store partial state to file + else if (bSave) { + std::string Json = WriteUpdate(pFiles); + MyWriteFile((wchar_t*)file_path.c_str(), (char*)Json.c_str(), Json.length()); + } + } + + if (bDoAddons && pAddons) { + std::wstring file_path = temp_dir + L"\\" _T(ADDONS_FILE); + if (step.empty() || step == L"apply" /*|| (ret == 0 && step != L"get")*/) + DeleteFileW(file_path.c_str()); + else if (bSave) { + std::string Json = WriteAddons(pAddons); + MyWriteFile((wchar_t*)file_path.c_str(), (char*)Json.c_str(), Json.length()); + } } } @@ -871,6 +1579,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // + PrintUsage(); return ERROR_INVALID; } diff --git a/SandboxieTools/UpdUtil/UpdUtil.h b/SandboxieTools/UpdUtil/UpdUtil.h index 47f33805..6475a08d 100644 --- a/SandboxieTools/UpdUtil/UpdUtil.h +++ b/SandboxieTools/UpdUtil/UpdUtil.h @@ -3,6 +3,8 @@ #define UPDATE_DOMAIN "sandboxie-plus.com" #define UPDATE_FILE "update.json" +#define ADDONS_FILE "addons.json" +#define ADDONS_PATH "\\addons\\" #define SCOPE_CORE_FILES L"32\\SbieDll.dll\0"\ @@ -33,5 +35,10 @@ #define ERROR_EXEC (-8) #define ERROR_CANCELED (-9) #define ERROR_INTERNAL (-10) // internal error, should not happen +#define ERROR_NO_ADDON (-11) // addon not found +#define ERROR_NO_ADDON2 (-12) // no addon for this paltform or framework +#define ERROR_BAD_ADDON (-13) +#define ERROR_BAD_ADDON2 (-14) +#define ERROR_DELETE (-15) diff --git a/SandboxieTools/UpdUtil/UpdUtil.vcxproj b/SandboxieTools/UpdUtil/UpdUtil.vcxproj index 7c0050ea..09b214e7 100644 --- a/SandboxieTools/UpdUtil/UpdUtil.vcxproj +++ b/SandboxieTools/UpdUtil/UpdUtil.vcxproj @@ -126,11 +126,12 @@ true _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true + false Windows true - ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies) + ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies);Version.lib @@ -147,7 +148,7 @@ true true true - ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies) + ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies);Version.lib @@ -156,11 +157,12 @@ true _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true + false Windows true - noenv.obj;ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies) + noenv.obj;ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies);Version.lib @@ -169,11 +171,12 @@ true _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true + false Windows true - noenv.obj;ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies) + noenv.obj;ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies);Version.lib @@ -190,7 +193,7 @@ true true true - noenv.obj;ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies) + noenv.obj;ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies);Version.lib @@ -207,7 +210,7 @@ true true true - noenv.obj;ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies) + noenv.obj;ntdll.lib;bcrypt.lib;wininet.lib;winhttp.lib;%(AdditionalDependencies);Version.lib