From de57cdedc522d894de75ea54faa4b700dca30d0a Mon Sep 17 00:00:00 2001 From: DavidXanatos Date: Sun, 19 Jul 2020 22:09:02 +0200 Subject: [PATCH] Build 0.3.5 / 5.42.1 --- CHANGELOG.md | 22 + Sandboxie/apps/common/RunBrowser.cpp | 4 +- Sandboxie/apps/control/LockConfigDialog.cpp | 10 +- Sandboxie/apps/control/MyFrame.cpp | 3 +- Sandboxie/common/my_version.h | 4 +- Sandboxie/core/dll/SboxDll32.def | 2 +- Sandboxie/core/dll/dllhook.c | 7 + Sandboxie/core/drv/process.c | 2 +- Sandboxie/install/ParseVersion.bat | 2 +- Sandboxie/install/SandboxieVS.nsi | 4 +- .../MiscHelpers/Common/ComboInputDialog.cpp | 10 + .../MiscHelpers/Common/ComboInputDialog.h | 3 +- SandboxiePlus/QSbieAPI/QSbieAPI.vcxproj | 6 +- .../QSbieAPI/QSbieAPI.vcxproj.filters | 14 +- SandboxiePlus/QSbieAPI/Sandboxie/SandBox.cpp | 4 +- SandboxiePlus/QSbieAPI/Sandboxie/SandBox.h | 6 +- .../Sandboxie/{IniSection.cpp => SbieIni.cpp} | 117 +- .../Sandboxie/{IniSection.h => SbieIni.h} | 17 +- SandboxiePlus/QSbieAPI/SbieAPI.cpp | 145 +- SandboxiePlus/QSbieAPI/SbieAPI.h | 16 + SandboxiePlus/SandMan/Forms/OptionsWindow.ui | 1126 ++++++++++++ SandboxiePlus/SandMan/Forms/SettingsWindow.ui | 362 ++++ SandboxiePlus/SandMan/Helpers/WinAdmin.cpp | 129 ++ SandboxiePlus/SandMan/Helpers/WinAdmin.h | 9 + .../SandMan/Resources/Actions/SetLogging.png | Bin 1298 -> 1105 bytes .../SandMan/Resources/Actions/config.png | Bin 0 -> 2203 bytes .../SandMan/Resources/Actions/empty_all.png | Bin 1310 -> 2199 bytes SandboxiePlus/SandMan/Resources/SandMan.png | Bin 10229 -> 7011 bytes SandboxiePlus/SandMan/Resources/SandMan.qrc | 1 + SandboxiePlus/SandMan/Resources/SandMan2.png | Bin 8164 -> 5698 bytes SandboxiePlus/SandMan/SandMan.cpp | 195 ++- SandboxiePlus/SandMan/SandMan.h | 24 +- SandboxiePlus/SandMan/SandMan.vcxproj | 12 + SandboxiePlus/SandMan/SandMan.vcxproj.filters | 38 + SandboxiePlus/SandMan/SbiePlusAPI.cpp | 5 +- SandboxiePlus/SandMan/Views/SbieView.cpp | 24 +- SandboxiePlus/SandMan/Views/SbieView.h | 2 + .../SandMan/Windows/OptionsWindow.cpp | 1502 +++++++++++++++++ SandboxiePlus/SandMan/Windows/OptionsWindow.h | 196 +++ .../SandMan/Windows/SettingsWindow.cpp | 168 ++ .../SandMan/Windows/SettingsWindow.h | 33 + SandboxiePlus/SandMan/main.cpp | 2 - 42 files changed, 4077 insertions(+), 149 deletions(-) rename SandboxiePlus/QSbieAPI/Sandboxie/{IniSection.cpp => SbieIni.cpp} (57%) rename SandboxiePlus/QSbieAPI/Sandboxie/{IniSection.h => SbieIni.h} (64%) create mode 100644 SandboxiePlus/SandMan/Forms/OptionsWindow.ui create mode 100644 SandboxiePlus/SandMan/Forms/SettingsWindow.ui create mode 100644 SandboxiePlus/SandMan/Helpers/WinAdmin.cpp create mode 100644 SandboxiePlus/SandMan/Helpers/WinAdmin.h create mode 100644 SandboxiePlus/SandMan/Resources/Actions/config.png create mode 100644 SandboxiePlus/SandMan/Windows/OptionsWindow.cpp create mode 100644 SandboxiePlus/SandMan/Windows/OptionsWindow.h create mode 100644 SandboxiePlus/SandMan/Windows/SettingsWindow.cpp create mode 100644 SandboxiePlus/SandMan/Windows/SettingsWindow.h diff --git a/CHANGELOG.md b/CHANGELOG.md index f504f77ccc..1bbdc92739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.3.5 / 5.42.1] - 2020-07-19 + +### Added +- Added settings window +- added translationsupport +- added dark theme +- added auto start option +- added sandbox options +- added debug option "NoAddProcessToJob=y" + +### Changed +- improved empty sandbox tray icon +- improved message parsing +- updated homepage links + +### Fixed +- fixed ini issue with sandman.exe when renaming sandboxes +- fixed ini auto reload bug introduced in the last build +- fixed issue when hooking delayd loaded libraries + + + ## [0.3 / 5.42] - 2020-07-04 ### Added diff --git a/Sandboxie/apps/common/RunBrowser.cpp b/Sandboxie/apps/common/RunBrowser.cpp index 9d45f98be3..a4b29798d5 100644 --- a/Sandboxie/apps/common/RunBrowser.cpp +++ b/Sandboxie/apps/common/RunBrowser.cpp @@ -134,7 +134,7 @@ void CRunBrowser::OnNo() CString CRunBrowser::GetTopicUrl(const CString &topic) { - return L"https://www.sandboxie.com/index.php?" + topic; + return L"https://xanasoft.com/Sandboxie/" + topic; } @@ -155,5 +155,5 @@ void CRunBrowser::OpenHelp(CWnd *pParentWnd, const CString &topic) void CRunBrowser::OpenForum(CWnd *pParentWnd) { - CRunBrowser x(pParentWnd, L"http://forums.sandboxie.com/phpBB3/"); + CRunBrowser x(pParentWnd, L"https://forum.xanasoft.com/"); } diff --git a/Sandboxie/apps/control/LockConfigDialog.cpp b/Sandboxie/apps/control/LockConfigDialog.cpp index cbb90a494a..82d617929f 100644 --- a/Sandboxie/apps/control/LockConfigDialog.cpp +++ b/Sandboxie/apps/control/LockConfigDialog.cpp @@ -246,11 +246,11 @@ void CLockConfigDialog::OnOK() ini.SetRestrictions( isEditAdminOnly, isForceDisableAdminOnly, isForgetPassword); - if ((*m_NewPassword) || isEditAdminOnly) { - int rv = CMyApp::MsgBox(this, MSG_4269, MB_YESNO); - if (rv == IDYES) - CRunBrowser::OpenHelp(this, L"ConfigurationProtection"); - } + //if ((*m_NewPassword) || isEditAdminOnly) { + // int rv = CMyApp::MsgBox(this, MSG_4269, MB_YESNO); + // if (rv == IDYES) + // CRunBrowser::OpenHelp(this, L"ConfigurationProtection"); + //} EndDialog(0); } diff --git a/Sandboxie/apps/control/MyFrame.cpp b/Sandboxie/apps/control/MyFrame.cpp index 5a9bc5eb25..9e68376a69 100644 --- a/Sandboxie/apps/control/MyFrame.cpp +++ b/Sandboxie/apps/control/MyFrame.cpp @@ -223,7 +223,8 @@ CMyFrame::CMyFrame(BOOL ForceVisible, BOOL ForceSync) AdjustSizePosition(left, top, width, height); ULONG exStyle = (CMyApp::m_LayoutRTL) ? WS_EX_LAYOUTRTL : 0; - CreateEx( exStyle, (LPCTSTR)CMyApp::m_atom, CMyApp::m_appTitle, + CString strTitle = CMyApp::m_appTitle + " - xanasoft.com"; + CreateEx( exStyle, (LPCTSTR)CMyApp::m_atom, strTitle, WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_SYSMENU, left, top, width, height, NULL, NULL, NULL); diff --git a/Sandboxie/common/my_version.h b/Sandboxie/common/my_version.h index 73140ed22c..37a84097ac 100644 --- a/Sandboxie/common/my_version.h +++ b/Sandboxie/common/my_version.h @@ -20,8 +20,8 @@ #ifndef _MY_VERSION_H #define _MY_VERSION_H -#define MY_VERSION_BINARY 5,42,0 -#define MY_VERSION_STRING "5.42.0" +#define MY_VERSION_BINARY 5,42,1 +#define MY_VERSION_STRING "5.42.1" #define MY_VERSION_COMPAT "5.42" // These #defines are used by either Resource Compiler, or by NSIC installer diff --git a/Sandboxie/core/dll/SboxDll32.def b/Sandboxie/core/dll/SboxDll32.def index c8b219e23d..3cdd716fb7 100644 --- a/Sandboxie/core/dll/SboxDll32.def +++ b/Sandboxie/core/dll/SboxDll32.def @@ -27,7 +27,7 @@ SbieApi_GetHomePath=_SbieApi_GetHomePath@16 SbieApi_GetUnmountHive=_SbieApi_GetUnmountHive@4 SbieApi_GetVersion=_SbieApi_GetVersion@4 ;;; SbieApi_GetWork=_SbieApi_GetWork@12 -SbieApi_GetMessage=_SbieApi_GetMessage@20 +SbieApi_GetMessage=_SbieApi_GetMessage@24 SbieApi_HookTramp=_SbieApi_HookTramp@8 diff --git a/Sandboxie/core/dll/dllhook.c b/Sandboxie/core/dll/dllhook.c index e9cd2abd61..82a2c78445 100644 --- a/Sandboxie/core/dll/dllhook.c +++ b/Sandboxie/core/dll/dllhook.c @@ -178,6 +178,12 @@ skip_e9_rewrite: ; SourceFunc = (void *)target; } + // + // this simplification fails for delay loaded libraries, see coments about SetSecurityInfo, + // resulting in an endless loop, so just dont do that + // + +#if 0 // // 64-bit only: if the function begins with 'jmp qword ptr [x]' // (6 bytes) then replace the value at x, rather than overwrite @@ -216,6 +222,7 @@ skip_e9_rewrite: ; return orig_addr; } +#endif #endif _WIN64 diff --git a/Sandboxie/core/drv/process.c b/Sandboxie/core/drv/process.c index 0c9e85dda5..edda776694 100644 --- a/Sandboxie/core/drv/process.c +++ b/Sandboxie/core/drv/process.c @@ -1024,7 +1024,7 @@ _FX void Process_NotifyProcess_Create( // don't put the process into a job if OpenWinClass=* // - if (new_proc->open_all_win_classes) { + if (new_proc->open_all_win_classes || Conf_Get_Boolean(box->name, L"NoAddProcessToJob", 0, FALSE)) { add_process_to_job = FALSE; } diff --git a/Sandboxie/install/ParseVersion.bat b/Sandboxie/install/ParseVersion.bat index 25221c7aa9..11017a031a 100644 --- a/Sandboxie/install/ParseVersion.bat +++ b/Sandboxie/install/ParseVersion.bat @@ -12,7 +12,7 @@ echo. > %OUTPUT% for /F "tokens=3*" %%A in ('findstr /R "^#define.SBIE_INSTALLER_PATH\>" %INPUT%') do ( echo ^^!define SBIE_INSTALLER_PATH %%A) >> %OUTPUT% -for /F "tokens=3*" %%A in ('findstr /R "^#define.MY_VERSION_STRING_EX\>" %INPUT%') do ( echo ^^!define VERSION %%A) >> %OUTPUT% +for /F "tokens=3*" %%A in ('findstr /R "^#define.MY_VERSION_STRING\>" %INPUT%') do ( echo ^^!define VERSION %%A) >> %OUTPUT% for /F "tokens=3*" %%A in ('findstr /R "^#define.MY_PRODUCT_NAME_STRING\>" %INPUT%') do ( set C=%%A %%B& echo ^^!define PRODUCT_FULL_NAME !C! & set C=!C: =!& echo ^^!define PRODUCT_NAME !C!) >> %OUTPUT% diff --git a/Sandboxie/install/SandboxieVS.nsi b/Sandboxie/install/SandboxieVS.nsi index ff83f5ea37..7d178c8674 100644 --- a/Sandboxie/install/SandboxieVS.nsi +++ b/Sandboxie/install/SandboxieVS.nsi @@ -28,7 +28,9 @@ SetCompressor /SOLID /FINAL lzma ; these are the build-time config settings. Need to be cmd line args or something better. ; pick either 32 or 64 bit ;!define _BUILDARCH Win32 -!define _BUILDARCH x64 +;!define _BUILDARCH x64 +!define _BUILDARCH "$%SBIE_BUILDARCH%" + ; uncomment this line if you want to make the special versions that download VC Redist ;!define INCLUDE_VCREDIST_DNLD diff --git a/SandboxiePlus/MiscHelpers/Common/ComboInputDialog.cpp b/SandboxiePlus/MiscHelpers/Common/ComboInputDialog.cpp index d90f3572bc..dc77e2448c 100644 --- a/SandboxiePlus/MiscHelpers/Common/ComboInputDialog.cpp +++ b/SandboxiePlus/MiscHelpers/Common/ComboInputDialog.cpp @@ -132,6 +132,11 @@ void CComboInputDialog::setValue(const QString &t) d->combo->setCurrentIndex(idx); } +int CComboInputDialog::findValue(const QString &t) const +{ + return d->combo->findText(t); +} + QVariant CComboInputDialog::data() const { return d->combo->currentData(); @@ -142,6 +147,11 @@ void CComboInputDialog::setData(const QVariant & v) d->combo->setCurrentIndex(d->combo->findData(v)); } +int CComboInputDialog::findData(const QVariant & v) const +{ + return d->combo->findData(v); +} + QPixmap CComboInputDialog::iconPixmap() const { if (const QPixmap *p = d->pixmapLabel->pixmap()) diff --git a/SandboxiePlus/MiscHelpers/Common/ComboInputDialog.h b/SandboxiePlus/MiscHelpers/Common/ComboInputDialog.h index bee2a39407..b2667c208c 100644 --- a/SandboxiePlus/MiscHelpers/Common/ComboInputDialog.h +++ b/SandboxiePlus/MiscHelpers/Common/ComboInputDialog.h @@ -28,10 +28,11 @@ class MISCHELPERS_EXPORT CComboInputDialog: public QDialog QString value() const; void setValue(const QString &); + int findValue(const QString &) const; QVariant data() const; void setData(const QVariant &); - + int findData(const QVariant &) const; QDialogButtonBox::StandardButtons standardButtons() const; void setStandardButtons(QDialogButtonBox::StandardButtons s); diff --git a/SandboxiePlus/QSbieAPI/QSbieAPI.vcxproj b/SandboxiePlus/QSbieAPI/QSbieAPI.vcxproj index bce3bbe30e..73d4ea12fb 100644 --- a/SandboxiePlus/QSbieAPI/QSbieAPI.vcxproj +++ b/SandboxiePlus/QSbieAPI/QSbieAPI.vcxproj @@ -185,9 +185,10 @@ + - + @@ -203,7 +204,8 @@ - + + diff --git a/SandboxiePlus/QSbieAPI/QSbieAPI.vcxproj.filters b/SandboxiePlus/QSbieAPI/QSbieAPI.vcxproj.filters index 418e655f6f..10d33791c2 100644 --- a/SandboxiePlus/QSbieAPI/QSbieAPI.vcxproj.filters +++ b/SandboxiePlus/QSbieAPI/QSbieAPI.vcxproj.filters @@ -39,12 +39,15 @@ Sandboxie - - Sandboxie - SbieAPI + + Sandboxie + + + Sandboxie + @@ -78,7 +81,10 @@ SbieAPI - + + Sandboxie + + Sandboxie diff --git a/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.cpp b/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.cpp index d2d0c8f71f..e8ffadf09d 100644 --- a/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.cpp +++ b/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.cpp @@ -23,7 +23,7 @@ //{ //}; -CSandBox::CSandBox(const QString& BoxName, class CSbieAPI* pAPI) : CIniSection(BoxName, pAPI) +CSandBox::CSandBox(const QString& BoxName, class CSbieAPI* pAPI) : CSbieIni(BoxName, pAPI) { //m = new SSandBox; @@ -41,7 +41,7 @@ CSandBox::CSandBox(const QString& BoxName, class CSbieAPI* pAPI) : CIniSection(B } else { - SetBool("AutoRecover", true); + SetBool("AutoRecover", false); SetBool("BlockNetworkFiles", true); //SetDefaultTemplates6(*this); // why 6? diff --git a/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.h b/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.h index e32a1989b3..c6fa475a2f 100644 --- a/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.h +++ b/SandboxiePlus/QSbieAPI/Sandboxie/SandBox.h @@ -21,9 +21,9 @@ #include "../qsbieapi_global.h" #include "BoxedProcess.h" -#include "IniSection.h" +#include "SbieIni.h" -class QSBIEAPI_EXPORT CSandBox : public CIniSection +class QSBIEAPI_EXPORT CSandBox : public CSbieIni { Q_OBJECT public: @@ -32,8 +32,6 @@ class QSBIEAPI_EXPORT CSandBox : public CIniSection virtual void UpdateDetails(); - virtual QString GetName() const { return m_Name; } - virtual QString GetFileRoot() const { return m_FilePath; } virtual QString GetRegRoot() const { return m_RegPath; } virtual QString GetIpcRoot() const { return m_IpcPath; } diff --git a/SandboxiePlus/QSbieAPI/Sandboxie/IniSection.cpp b/SandboxiePlus/QSbieAPI/Sandboxie/SbieIni.cpp similarity index 57% rename from SandboxiePlus/QSbieAPI/Sandboxie/IniSection.cpp rename to SandboxiePlus/QSbieAPI/Sandboxie/SbieIni.cpp index 4403455032..3c88b64213 100644 --- a/SandboxiePlus/QSbieAPI/Sandboxie/IniSection.cpp +++ b/SandboxiePlus/QSbieAPI/Sandboxie/SbieIni.cpp @@ -16,7 +16,7 @@ * along with this program. If not, see . */ #include "stdafx.h" -#include "IniSection.h" +#include "SbieIni.h" #include "../SbieAPI.h" #include @@ -25,39 +25,39 @@ typedef long NTSTATUS; #include "..\..\Sandboxie\core\drv\api_flags.h" -CIniSection::CIniSection(const QString& Section, class CSbieAPI* pAPI, QObject* parent) : QObject(parent) +CSbieIni::CSbieIni(const QString& Section, class CSbieAPI* pAPI, QObject* parent) : QObject(parent) { m_Name = Section; m_pAPI = pAPI; } -CIniSection::~CIniSection() +CSbieIni::~CSbieIni() { } -SB_STATUS CIniSection::SetText(const QString& Setting, const QString& Value) +SB_STATUS CSbieIni::SetText(const QString& Setting, const QString& Value) { if (GetText(Setting) == Value) return SB_OK; return m_pAPI->SbieIniSet(m_Name, Setting, Value); } -SB_STATUS CIniSection::SetNum(const QString& Setting, int Value) +SB_STATUS CSbieIni::SetNum(const QString& Setting, int Value) { return SetText(Setting, QString::number(Value)); } -SB_STATUS CIniSection::SetNum64(const QString& Setting, __int64 Value) +SB_STATUS CSbieIni::SetNum64(const QString& Setting, __int64 Value) { return SetText(Setting, QString::number(Value)); } -SB_STATUS CIniSection::SetBool(const QString& Setting, bool Value) +SB_STATUS CSbieIni::SetBool(const QString& Setting, bool Value) { return SetText(Setting, Value ? "y" : "n"); } -QString CIniSection::GetText(const QString& Setting, const QString& Default) const +QString CSbieIni::GetText(const QString& Setting, const QString& Default) const { int flags = (m_Name.isEmpty() ? 0 : CONF_GET_NO_GLOBAL) | CONF_GET_NO_EXPAND; QString Value = m_pAPI->SbieIniGet(m_Name, Setting, flags); @@ -65,7 +65,7 @@ QString CIniSection::GetText(const QString& Setting, const QString& Default) con return Value; } -int CIniSection::GetNum(const QString& Setting, int Default) const +int CSbieIni::GetNum(const QString& Setting, int Default) const { QString StrValue = GetText(Setting); bool ok; @@ -74,7 +74,7 @@ int CIniSection::GetNum(const QString& Setting, int Default) const return Value; } -__int64 CIniSection::GetNum64(const QString& Setting, __int64 Default) const +__int64 CSbieIni::GetNum64(const QString& Setting, __int64 Default) const { QString StrValue = GetText(Setting); bool ok; @@ -83,7 +83,7 @@ __int64 CIniSection::GetNum64(const QString& Setting, __int64 Default) const return Value; } -bool CIniSection::GetBool(const QString& Setting, bool Default) const +bool CSbieIni::GetBool(const QString& Setting, bool Default) const { QString StrValue = GetText(Setting); if (StrValue.compare("y", Qt::CaseInsensitive) == 0) @@ -93,15 +93,15 @@ bool CIniSection::GetBool(const QString& Setting, bool Default) const return Default; } -QStringList CIniSection::GetTextList(const QString &Setting, bool withBrackets) +QStringList CSbieIni::GetTextList(const QString &Setting, bool withTemplates) const { QStringList TextList; int flags = (m_Name.isEmpty() ? 0 : CONF_GET_NO_GLOBAL) | CONF_GET_NO_EXPAND; - if (withBrackets) + if (!withTemplates) flags |= CONF_GET_NO_TEMPLS; - for(int index = 0; ; index++) + for (int index = 0; ; index++) { QString Value = m_pAPI->SbieIniGet(m_Name, Setting, index | flags); if (Value.isNull()) @@ -112,35 +112,80 @@ QStringList CIniSection::GetTextList(const QString &Setting, bool withBrackets) return TextList; } -SB_STATUS CIniSection::InsertText(const QString& Setting, const QString& Value) +SB_STATUS CSbieIni::UpdateTextList(const QString &Setting, const QStringList& List) +{ + QStringList OldSettings = GetTextList(Setting); + QStringList NewSettings; + foreach(const QString& Value, List) { + if (!OldSettings.removeOne(Value)) + NewSettings.append(Value); + } + // delete removed or changed settings + foreach(const QString& Value, OldSettings) + DelValue(Setting, Value); + // add new or changed settings + foreach(const QString& Value, NewSettings) + InsertText(Setting, Value); + return SB_OK; +} + +QStringList CSbieIni::GetTemplates() const +{ + QStringList Templates; + + for (int tmpl_index = 0; ; tmpl_index++) + { + QString TmplName = m_pAPI->SbieIniGet(m_Name, "Template", tmpl_index | CONF_GET_NO_TEMPLS); + if (TmplName.isNull()) + break; + Templates.append(TmplName); + } + + return Templates; +} + +QStringList CSbieIni::GetTextListTmpl(const QString &Setting, const QString& Template) const +{ + QStringList TextList; + + for (int index = 0; ; index++) + { + QString Value = m_pAPI->SbieIniGet("Template_" + Template, Setting, index | CONF_GET_NO_GLOBAL); + if (Value.isNull()) + break; + TextList.append(Value); + } + + return TextList; +} + +SB_STATUS CSbieIni::InsertText(const QString& Setting, const QString& Value) { return m_pAPI->SbieIniSet(m_Name, Setting, Value, CSbieAPI::eIniInsert); } -SB_STATUS CIniSection::AppendText(const QString& Setting, const QString& Value) +SB_STATUS CSbieIni::AppendText(const QString& Setting, const QString& Value) { return m_pAPI->SbieIniSet(m_Name, Setting, Value, CSbieAPI::eIniAppend); } -SB_STATUS CIniSection::DelValue(const QString& Setting, const QString& Value) +SB_STATUS CSbieIni::DelValue(const QString& Setting, const QString& Value) { return m_pAPI->SbieIniSet(m_Name, Setting, Value, CSbieAPI::eIniDelete); } - -SB_STATUS CIniSection::RenameSection( const QString& NewName, bool deleteOld) // Note: deleteOld is used when duplicating a box +QList> CSbieIni::GetIniSection(qint32* pStatus, bool withTemplates) const { - if (m_Name.isEmpty() || NewName.isEmpty()) - return SB_ERR(); - bool SameName = (bool)(NewName.compare(m_Name, Qt::CaseInsensitive) == 0); - qint32 status = STATUS_SUCCESS; - // Get all Settigns + int flags = CONF_GET_NO_EXPAND; + if (!withTemplates) + flags |= CONF_GET_NO_TEMPLS; + QList> Settings; for (int setting_index = 0; ; setting_index++) { - QString setting_name = m_pAPI->SbieIniGet(m_Name, NULL, setting_index | CONF_GET_NO_TEMPLS | CONF_GET_NO_EXPAND, &status); + QString setting_name = m_pAPI->SbieIniGet(m_Name, "", setting_index | flags, &status); if (status == STATUS_RESOURCE_NAME_NOT_FOUND) { status = STATUS_SUCCESS; break; @@ -150,7 +195,7 @@ SB_STATUS CIniSection::RenameSection( const QString& NewName, bool deleteOld) // for (int value_index = 0; ; value_index++) { - QString setting_value = m_pAPI->SbieIniGet(m_Name, setting_name, value_index | CONF_GET_NO_GLOBAL | CONF_GET_NO_TEMPLS | CONF_GET_NO_EXPAND, &status); + QString setting_value = m_pAPI->SbieIniGet(m_Name, setting_name, value_index | CONF_GET_NO_GLOBAL | flags, &status); if (status == STATUS_RESOURCE_NAME_NOT_FOUND) { status = STATUS_SUCCESS; break; @@ -165,13 +210,27 @@ SB_STATUS CIniSection::RenameSection( const QString& NewName, bool deleteOld) // break; } + if (pStatus) *pStatus = status; + return Settings; +} + +SB_STATUS CSbieIni::RenameSection( const QString& NewName, bool deleteOld) // Note: deleteOld is used when duplicating a box +{ + qint32 status = STATUS_SUCCESS; + + if (m_Name.isEmpty() || NewName.isEmpty()) + return SB_ERR(); + bool SameName = (bool)(NewName.compare(m_Name, Qt::CaseInsensitive) == 0); + + // Get all Settigns + QList> Settings = GetIniSection(&status); if (status != STATUS_SUCCESS) return SB_ERR(CSbieAPI::tr("Failed to copy configuration from sandbox %1: %2").arg(m_Name).arg(status, 8, 16), status); // check if such a box already exists if (!SameName) { - m_pAPI->SbieIniGet(NewName, NULL, CONF_GET_NO_EXPAND, &status); + m_pAPI->SbieIniGet(NewName, "", CONF_GET_NO_EXPAND, &status); if (status != STATUS_RESOURCE_NAME_NOT_FOUND) return SB_ERR(CSbieAPI::tr("A sandbox of the name %1 already exists").arg(NewName)); } @@ -185,7 +244,7 @@ SB_STATUS CIniSection::RenameSection( const QString& NewName, bool deleteOld) // // Apply all Settigns for (QList>::iterator I = Settings.begin(); I != Settings.end(); ++I) { - SB_STATUS Status = m_pAPI->SbieIniSet(NewName, I->first, I->second); + SB_STATUS Status = m_pAPI->SbieIniSet(NewName, I->first, I->second, CSbieAPI::eIniInsert); if (Status.IsError()) return Status; } @@ -206,7 +265,7 @@ SB_STATUS CIniSection::RenameSection( const QString& NewName, bool deleteOld) // return SB_OK; } -SB_STATUS CIniSection::RemoveSection() +SB_STATUS CSbieIni::RemoveSection() { return m_pAPI->SbieIniSet(m_Name, "*", ""); } \ No newline at end of file diff --git a/SandboxiePlus/QSbieAPI/Sandboxie/IniSection.h b/SandboxiePlus/QSbieAPI/Sandboxie/SbieIni.h similarity index 64% rename from SandboxiePlus/QSbieAPI/Sandboxie/IniSection.h rename to SandboxiePlus/QSbieAPI/Sandboxie/SbieIni.h index bcd80c534a..a3e5b27acc 100644 --- a/SandboxiePlus/QSbieAPI/Sandboxie/IniSection.h +++ b/SandboxiePlus/QSbieAPI/Sandboxie/SbieIni.h @@ -5,12 +5,14 @@ #include "../SbieError.h" -class QSBIEAPI_EXPORT CIniSection: public QObject +class QSBIEAPI_EXPORT CSbieIni: public QObject { Q_OBJECT public: - CIniSection(const QString& Section, class CSbieAPI* pAPI, QObject* parent = 0); - virtual ~CIniSection(); + CSbieIni(const QString& Section, class CSbieAPI* pAPI, QObject* parent = 0); + virtual ~CSbieIni(); + + virtual QString GetName() const { return m_Name; } virtual SB_STATUS SetText(const QString& Setting, const QString& Value); virtual SB_STATUS SetNum(const QString& Setting, int Value); @@ -22,16 +24,23 @@ class QSBIEAPI_EXPORT CIniSection: public QObject virtual __int64 GetNum64(const QString& Setting, __int64 Default = 0) const; virtual bool GetBool(const QString& Setting, bool Default = false) const; - virtual QStringList GetTextList(const QString &Setting, bool withBrackets = false); + virtual QStringList GetTextList(const QString &Setting, bool withTemplates = true) const; + virtual SB_STATUS UpdateTextList(const QString &Setting, const QStringList& List); + virtual QStringList GetTemplates() const; + virtual QStringList GetTextListTmpl(const QString &Setting, const QString& Template) const; virtual SB_STATUS InsertText(const QString& Setting, const QString& Value); virtual SB_STATUS AppendText(const QString& Setting, const QString& Value); virtual SB_STATUS DelValue(const QString& Setting, const QString& Value); + virtual QList> GetIniSection(qint32* pStatus = NULL, bool withTemplates = false) const; + virtual SB_STATUS RenameSection(const QString& NewName, bool deleteOld = true); virtual SB_STATUS RemoveSection(); + CSbieAPI* GetAPI() { return m_pAPI; } + protected: QString m_Name; diff --git a/SandboxiePlus/QSbieAPI/SbieAPI.cpp b/SandboxiePlus/QSbieAPI/SbieAPI.cpp index 6ab7ccc5d8..0c8398a346 100644 --- a/SandboxiePlus/QSbieAPI/SbieAPI.cpp +++ b/SandboxiePlus/QSbieAPI/SbieAPI.cpp @@ -72,7 +72,7 @@ struct SSbieAPI ULONG SizeofPortMsg; ULONG CallSeqNumber; - QString Password; // todo: suppor lcoked configurations + QString Password; ULONG sessionId; @@ -111,6 +111,8 @@ CSbieAPI::CSbieAPI(QObject* parent) : QThread(parent) { m = new SSbieAPI(); + m_pGlobalSection = new CSbieIni("GlobalSettings", this, this); + m_bReloadPending = false; connect(&m_IniWatcher, SIGNAL(fileChanged(const QString&)), this, SLOT(OnIniChanged(const QString&))); @@ -398,6 +400,11 @@ SB_STATUS CSbieAPI__CallServer(SSbieAPI* m, MSG_HEADER* req, MSG_HEADER** prpl) SB_STATUS CSbieAPI::CallServer(void* req, void* rpl) const { + // + // Note: Once we open a port to the server from a threat the service will remember it we can't reconnect after disconnection + // So for every new connection we need a new threat, we achive this by letting our monitor threat issue all requests + // + while (InterlockedCompareExchange(&m->SvcLock, SVC_OP_STATE_PREP, SVC_OP_STATE_IDLE) != SVC_OP_STATE_IDLE) QThread::msleep(1); @@ -535,6 +542,7 @@ void CSbieAPI::OnIniChanged(const QString &path) void CSbieAPI::OnReloadConfig() { + m_bReloadPending = false; ReloadConfig(); } @@ -668,7 +676,9 @@ SB_STATUS CSbieAPI::ReloadBoxes() SB_STATUS CSbieAPI::SbieIniSet(void *RequestBuf, void *pPasswordWithinRequestBuf, const QString& SectionName, const QString& SettingName) { +retry: m->Password.toWCharArray((WCHAR*)pPasswordWithinRequestBuf); // fix-me: potential overflow + ((WCHAR*)pPasswordWithinRequestBuf)[m->Password.length()] = L'\0'; MSG_HEADER *rpl = NULL; SB_STATUS Status = CSbieAPI::CallServer((MSG_HEADER *)RequestBuf, &rpl); @@ -679,8 +689,17 @@ SB_STATUS CSbieAPI::SbieIniSet(void *RequestBuf, void *pPasswordWithinRequestBuf free(rpl); if (status == 0) return SB_OK; - if (status == STATUS_LOGON_NOT_GRANTED || status == STATUS_WRONG_PASSWORD) + if (status == STATUS_LOGON_NOT_GRANTED || status == STATUS_WRONG_PASSWORD) + { + if (((MSG_HEADER *)RequestBuf)->msgid != MSGID_SBIE_INI_TEST_PASSWORD) + { + bool bRetry = false; + emit NotAuthorized(status == STATUS_WRONG_PASSWORD, bRetry); + if (bRetry) + goto retry; + } return SB_ERR(CSbieAPI::tr("You are not authorized to update configuration in section '%1'").arg(SectionName), status); + } return SB_ERR(CSbieAPI::tr("Failed to set configuration setting %1 in section %2: %3").arg(SettingName).arg(SectionName).arg(status, 8, 16), status); } @@ -1179,6 +1198,43 @@ bool CSbieAPI::IsBoxEnabled(const QString& BoxName) return NT_SUCCESS(m->IoControl(parms)); } +bool CSbieAPI::IsConfigLocked() +{ + return m->Password.isEmpty() && !SbieIniGet("GlobalSettings", "EditPassword", 0).isEmpty(); +} + +SB_STATUS CSbieAPI::UnlockConfig(const QString& Password) +{ + SBIE_INI_PASSWORD_REQ *req = (SBIE_INI_PASSWORD_REQ *)malloc(REQUEST_LEN); + req->h.msgid = MSGID_SBIE_INI_TEST_PASSWORD; + req->h.length = sizeof(SBIE_INI_PASSWORD_REQ); + m->Password = Password; + SB_STATUS Status = SbieIniSet(req, req->old_password, "GlobalSettings", "*"); + if (Status.IsError()) + m->Password.clear(); + free(req); + return Status; +} + +SB_STATUS CSbieAPI::LockConfig(const QString& NewPassword) +{ + SBIE_INI_PASSWORD_REQ *req = (SBIE_INI_PASSWORD_REQ *)malloc(REQUEST_LEN); + req->h.msgid = MSGID_SBIE_INI_SET_PASSWORD; + req->h.length = sizeof(SBIE_INI_PASSWORD_REQ); + m->Password.toWCharArray(req->new_password); // fix-me: potential overflow + req->new_password[m->Password.length()] = L'\0'; + SB_STATUS Status = SbieIniSet(req, req->old_password, "GlobalSettings", "*"); + if (!Status.IsError()) + m->Password = NewPassword; + free(req); + return Status; +} + +void CSbieAPI::ClearPassword() +{ + m->Password.clear(); +} + /////////////////////////////////////////////////////////////////////////////// // Log // @@ -1213,7 +1269,7 @@ bool CSbieAPI::GetLog() wchar_t* Buffer[4*1024]; ULONG Length = ARRAYSIZE(Buffer); - ULONG MessageId = 0; + ULONG MsgCode = 0; ULONG ProcessId = 0; ULONG MessageNum = m->lastMessageNum; @@ -1225,7 +1281,7 @@ bool CSbieAPI::GetLog() args->func_code = API_GET_MESSAGE; args->msg_num.val = &MessageNum; args->session_id.val = m->sessionId; - args->msgid.val = &MessageId; + args->msgid.val = &MsgCode; args->msgtext.val = &msgtext; args->process_id.val = &ProcessId; @@ -1237,7 +1293,7 @@ bool CSbieAPI::GetLog() // we missed something m->lastMessageNum = MessageNum; - if (MessageId == 0) + if (MsgCode == 0) return true; // empty dummy message for maintaining sequence consistency WCHAR *str1 = (WCHAR*)msgtext.Buffer; @@ -1245,7 +1301,36 @@ bool CSbieAPI::GetLog() WCHAR *str2 = str1 + str1_len + 1; ULONG str2_len = wcslen(str2); - QString Message = CSbieAPI__FormatSbieMsg(m, MessageId, str1, str2); + // + // 0xTFFFMMMM + // + // T = ttcr + // tt = 00 - Ok + // tt = 01 - Info + // tt = 10 - Warning + // tt = 11 - Error + // c = unused + // r = reserved + // + // FFF = 0x000 UIstr + // FFF = 0x101 POPUP + // FFF = 0x102 EVENT + // + // MMMM = Message Code + // + quint8 Severity = MsgCode >> 30; + quint16 Facility = (MsgCode >> 16) & 0x0FFF; + quint16 MessageId= MsgCode & 0xFFFF; + + if (MessageId == 2199) // Auto Recovery notification + { + QString TempStr = QString::fromWCharArray(str1); + int TempPos = TempStr.indexOf(" "); + FileToRecover(TempStr.left(TempPos), Nt2DosPath(TempStr.mid(TempPos + 1))); + return true; + } + + QString Message = CSbieAPI__FormatSbieMsg(m, MsgCode, str1, str2); if(ProcessId != 4) // if its not from the driver add the pid Message += tr(" by process: %1").arg(ProcessId); emit LogMessage(Message); @@ -1253,6 +1338,45 @@ bool CSbieAPI::GetLog() return true; } +/////////////////////////////////////////////////////////////////////////////// +// Forced Processes +// + +SB_STATUS CSbieAPI::DisableForceProcess(bool Set) +{ + //m_pGlobalSection->SetNum("ForceDisableSeconds", Seconds); + + ULONG uEnable = Set ? TRUE : FALSE; + + __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; + API_DISABLE_FORCE_PROCESS_ARGS* args = (API_DISABLE_FORCE_PROCESS_ARGS*)parms; + + memset(parms, 0, sizeof(parms)); + args->func_code = API_DISABLE_FORCE_PROCESS; + args->set_flag.val = &uEnable; // NewState + args->get_flag.val = NULL; // OldState + + NTSTATUS status = m->IoControl(parms); + if (!NT_SUCCESS(status)) + return SB_ERR(status); + return SB_OK; +} + +bool CSbieAPI::AreForceProcessDisabled() +{ + ULONG uEnabled = FALSE; + + __declspec(align(8)) ULONG64 parms[API_NUM_ARGS]; + API_DISABLE_FORCE_PROCESS_ARGS* args = (API_DISABLE_FORCE_PROCESS_ARGS*)parms; + + memset(parms, 0, sizeof(parms)); + args->func_code = API_DISABLE_FORCE_PROCESS; + args->set_flag.val = NULL; // NewState + args->get_flag.val = &uEnabled; // OldState + + return NT_SUCCESS(m->IoControl(parms)) && uEnabled; +} + /////////////////////////////////////////////////////////////////////////////// // Monitor // @@ -1326,6 +1450,15 @@ bool CSbieAPI::GetMonitor() return true; } +/////////////////////////////////////////////////////////////////////////////// +// Other +// + +QString CSbieAPI::GetSbieMessage(int MessageId, const QString& arg1, const QString& arg2) const +{ + return CSbieAPI__FormatSbieMsg(m, MessageId, arg1.toStdWString().c_str(), arg2.toStdWString().c_str()); +} + /////////////////////////////////////////////////////////////////////////////// // // diff --git a/SandboxiePlus/QSbieAPI/SbieAPI.h b/SandboxiePlus/QSbieAPI/SbieAPI.h index 83c1bcda0c..1ec3587005 100644 --- a/SandboxiePlus/QSbieAPI/SbieAPI.h +++ b/SandboxiePlus/QSbieAPI/SbieAPI.h @@ -108,6 +108,15 @@ class QSBIEAPI_EXPORT CSbieAPI : public QThread virtual QString SbieIniGet(const QString& Section, const QString& Setting, quint32 Index = 0, qint32* ErrCode = NULL); virtual SB_STATUS SbieIniSet(const QString& Section, const QString& Setting, const QString& Value, ESetMode Mode = eIniUpdate); virtual bool IsBoxEnabled(const QString& BoxName); + virtual CSbieIni* GetGlobalSettings() const { return m_pGlobalSection; } + virtual bool IsConfigLocked(); + virtual SB_STATUS UnlockConfig(const QString& Password); + virtual SB_STATUS LockConfig(const QString& NewPassword); + virtual void ClearPassword(); + + // Forced Processes + virtual SB_STATUS DisableForceProcess(bool Set); + virtual bool AreForceProcessDisabled(); // Monitor virtual SB_STATUS EnableMonitor(bool Enable); @@ -116,9 +125,14 @@ class QSBIEAPI_EXPORT CSbieAPI : public QThread virtual QList GetResLog() const { QReadLocker Lock(&m_ResLogMutex); return m_ResLogList; } virtual void ClearResLog() { QWriteLocker Lock(&m_ResLogMutex); m_ResLogList.clear(); } + // Other + virtual QString GetSbieMessage(int MessageId, const QString& arg1 = QString(), const QString& arg2 = QString()) const; + signals: void StatusChanged(); void LogMessage(const QString& Message, bool bNotify = true); + void FileToRecover(const QString& BoxName, const QString& FilePath); + void NotAuthorized(bool bLoginRequired, bool &bRetry); private slots: //virtual void OnMonitorEntry(quint64 ProcessId, quint32 Type, const QString& Value); @@ -169,6 +183,8 @@ private slots: bool m_bTerminate; + CSbieIni* m_pGlobalSection; + private: mutable QMutex m_ThreadMutex; mutable QWaitCondition m_ThreadWait; diff --git a/SandboxiePlus/SandMan/Forms/OptionsWindow.ui b/SandboxiePlus/SandMan/Forms/OptionsWindow.ui new file mode 100644 index 0000000000..5961a002a7 --- /dev/null +++ b/SandboxiePlus/SandMan/Forms/OptionsWindow.ui @@ -0,0 +1,1126 @@ + + + OptionsWindow + + + + 0 + 0 + 614 + 397 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + SandboxiePlus Options + + + + + + + + + true + + + + QTabWidget::West + + + 0 + + + + General Options + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 75 + true + + + + Appearance + + + + + + + Copy file size limit: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 75 + 16777215 + + + + + + + + + 75 + true + + + + File Mrigration + + + + + + + Issue message 2102 when a file is too large + + + + + + + kilobytes + + + + + + + + + + + + + Sandbox Indicator in title: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Sandboxed window border: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + + + + Program Groups + + + + + + + + You can group programs together and give them a group name. Program groups can be used with some of the settings instead of program names. + + + true + + + + + + + Add Group + + + + + + + Add Program + + + + + + + Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + + Name + + + + + + + + + + + Forced Programs + + + + 9 + + + 9 + + + 9 + + + 9 + + + + + + + Remove + + + + + + + Force Folder + + + + + + + true + + + + Type + + + + + Path + + + + + + + + Programs enteres here, or programs started from entered locations, will be put in this sandbox automatically, unless thay are explicitly started in an otehr sandbox. + + + true + + + + + + + Force Program + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Show Templates + + + + + + + + + + Stop Behavioure + + + + + + + + Remove Progam + + + + + + + Lingering programs will be automatically terminated is thay are still running after all other processes have terminated. + +If leader processes are defined all others are threated as lingering processes. + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Add Leader Program + + + + + + + Add Lingering Program + + + + + + + true + + + + Type + + + + + Path + + + + + + + + Show Templates + + + + + + + + + + Start Restrictions + + + + + + + + Add Program + + + + + + + true + + + + Name + + + + + + + + Issue message 1308 when a program fails to start + + + + + + + Note: Programs installed to this sandbox wont be able to start at all. + + + true + + + + + + + Remove Program + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Allow only sellected programs to start in this sandbox. + + + + + + + + + + Other Restrictions + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 75 + true + + + + Protect the system from sandboxed processes + + + General restrictions + + + + + + + + 75 + true + + + + Protect the sandbox integrity itself + + + Sandbox protection + + + + + + + Drop elevated privileges from processes + + + + + + + Block network files and folders, unless pecifically opened. + + + + + + + Don't open default COM objects + + + + + + + Limit access to the emulated service controll manager to privileged processes + + + + + + + Start the sandboxed RpcSs as a SYSTEM process + + + + + + + Protect sandboxed SYSTEM processes from unprivileged unsandboxed processes + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Internet Access + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Remove Program + + + + + + + Issue message 1307 when a program are denied internet + + + + + + + Add Program + + + + + + + true + + + + Name + + + + + + + + Block internet access for all programs except those added to the list. + + + + + + + Note: Programs installed to this sandbox wont be able access the internet at all. + + + + + + + + + + Resource Access + + + + + + + + true + + + + Type + + + + + Program + + + + + Access + + + + + Path + + + + + + + + Add Reg Key + + + + + + + Add File/Folder + + + + + + + Remove + + + + + + + Add Wnd Class + + + + + + + Add COM Object + + + + + + + Add IPC Path + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Move Up + + + + + + + Move Down + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Show Templates + + + + + + + Configure which processec can access what resoutrces. +Double click on an entry to edit it. +Note: Not all access modes are available to processes instaleld into a sandbox. + + + + + + + + + + Advanced Options + + + + + + + + + 75 + true + + + + Allow only sellected users to use this sandbox + + + User restrictions + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Delete command: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Add User + + + + + + + + + + Auto delete content when last sandboxed process terminates + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Protect this sandbox from deletion or emptying + + + + + + + + 75 + true + + + + Delete options + + + + + + + Remove User + + + + + + + + + + App Templates + + + + + + + + This list contains a large amount of sandbox comatybility enchancing templates + + + true + + + + + + + + + + true + + + + Category + + + + + Name + + + + + + + + Filter Categories + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Edit ini Section + + + + + + Edit ini + + + false + + + + + + + false + + + Cancel + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Save + + + + + + + QPlainTextEdit::NoWrap + + + + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + diff --git a/SandboxiePlus/SandMan/Forms/SettingsWindow.ui b/SandboxiePlus/SandMan/Forms/SettingsWindow.ui new file mode 100644 index 0000000000..ab68058095 --- /dev/null +++ b/SandboxiePlus/SandMan/Forms/SettingsWindow.ui @@ -0,0 +1,362 @@ + + + SettingsWindow + + + + 0 + 0 + 573 + 451 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + SandboxiePlus Settings + + + + + + + + + true + + + + QTabWidget::North + + + 1 + + + + General Options + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Show Sys-Tray + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 75 + true + + + + Tray options + + + + + + + Restart required (!) + + + Qt::AlignCenter + + + + + + + Start with Windows + + + + + + + + + + + + + Use Dark Theme + + + + + + + Show Notifications for relevant log Messages + + + false + + + + + + + On main window close: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Watch Sandboxie.ini for changes + + + + + + + + + + Advanced Options + + + + + + + + Only Administrator user accounts can use Disable Forced Programs command + + + + + + + Only Administrator user accounts can make changes + + + + + + + + 75 + true + + + + Config protection + + + + + + + Password must be entered in order to make changes + + + + + + + Change Password + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 75 + true + + + + Sandbox default + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Sandbox file system root: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Sandbox ipc root: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Sandbox registry root: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Separate user folders + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Clear password when main window becomes hidden + + + + + + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + diff --git a/SandboxiePlus/SandMan/Helpers/WinAdmin.cpp b/SandboxiePlus/SandMan/Helpers/WinAdmin.cpp new file mode 100644 index 0000000000..7843f5668c --- /dev/null +++ b/SandboxiePlus/SandMan/Helpers/WinAdmin.cpp @@ -0,0 +1,129 @@ +#include "stdafx.h" +#include "WinAdmin.h" + +#include +#include +#include + +bool IsElevated() +{ + bool fRet = false; + HANDLE hToken = NULL; + if(OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&hToken)) + { + TOKEN_ELEVATION Elevation; + DWORD cbSize = sizeof(TOKEN_ELEVATION); + if(GetTokenInformation(hToken, TokenElevation, &Elevation, sizeof( Elevation ), &cbSize)) + fRet = Elevation.TokenIsElevated; + } + if(hToken) + CloseHandle(hToken); + return fRet; +} + +int RunElevated(const wstring& Params, bool bGetCode) +{ + wchar_t szPath[MAX_PATH]; + if (!GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath))) + return -3; + return RunElevated(wstring(szPath), Params, bGetCode); +} + +int RunElevated(const wstring& binaryPath, const wstring& Params, bool bGetCode) +{ + // Launch itself as admin + SHELLEXECUTEINFO sei = { sizeof(sei) }; + sei.fMask = SEE_MASK_NOCLOSEPROCESS; + sei.lpVerb = L"runas"; + sei.lpFile = binaryPath.c_str(); + sei.lpParameters = Params.c_str(); + sei.hwnd = NULL; + sei.nShow = SW_NORMAL; + if (!ShellExecuteEx(&sei)) + { + DWORD dwError = GetLastError(); + if (dwError == ERROR_CANCELLED) + return -2; // The user refused to allow privileges elevation. + } + else + { + if (bGetCode) + { + WaitForSingleObject(sei.hProcess, 10000); + DWORD ExitCode = -4; + BOOL success = GetExitCodeProcess(sei.hProcess, &ExitCode); + CloseHandle(sei.hProcess); + return success ? ExitCode : -4; + } + return 0; + } + return -1; +} + +int RestartElevated(int &argc, char **argv) +{ + wstring Params; + for (int i = 1; i < argc; i++) + { + if (i > 1) + Params.append(L" "); + Params.append(L"\"" + wstring_convert>().from_bytes(argv[i]) + L"\""); + } + return RunElevated(Params); +} + +////////////////////////////////////////////////////////////////////////////////// +// AutoRun + +#define APP_NAME L"SandboxiePlus" + +#define AUTO_RUN_KEY_NAME APP_NAME L"_AutoRun" + +bool IsAutorunEnabled() +{ + bool result = false; + + HKEY hkey = nullptr; + if (RegOpenKeyEx (HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS) + { + WCHAR buffer[MAX_PATH] = {0}; + DWORD size = _countof (buffer); + + if (RegQueryValueEx (hkey, AUTO_RUN_KEY_NAME, nullptr, nullptr, (LPBYTE)buffer, &size) == ERROR_SUCCESS) + { + result = true; // todo: check path + } + + RegCloseKey (hkey); + } + + return result; +} + +bool AutorunEnable (bool is_enable) +{ + bool result = false; + + HKEY hkey = nullptr; + if (RegOpenKeyEx (HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS) + { + if (is_enable) + { + wchar_t szPath[MAX_PATH]; + if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath))) + { + wstring path = L"\"" + wstring(szPath) + L"\" -autorun"; + + result = (RegSetValueEx(hkey, AUTO_RUN_KEY_NAME, 0, REG_SZ, (LPBYTE)path.c_str(), DWORD((path.length() + 1) * sizeof(WCHAR))) == ERROR_SUCCESS); + } + } + else + { + result = (RegDeleteValue (hkey, AUTO_RUN_KEY_NAME) == ERROR_SUCCESS); + } + + RegCloseKey (hkey); + } + + return false; +} \ No newline at end of file diff --git a/SandboxiePlus/SandMan/Helpers/WinAdmin.h b/SandboxiePlus/SandMan/Helpers/WinAdmin.h new file mode 100644 index 0000000000..1195504a12 --- /dev/null +++ b/SandboxiePlus/SandMan/Helpers/WinAdmin.h @@ -0,0 +1,9 @@ +#pragma once + +bool IsElevated(); +int RunElevated(const wstring& Params, bool bGetCode = false); +int RunElevated(const wstring& binaryPath, const wstring& Params, bool bGetCode = false); +int RestartElevated(int &argc, char **argv); + +bool IsAutorunEnabled(); +bool AutorunEnable(bool is_enable); diff --git a/SandboxiePlus/SandMan/Resources/Actions/SetLogging.png b/SandboxiePlus/SandMan/Resources/Actions/SetLogging.png index f769c083771a6fc41ce4b94f4a0026068af38a3b..1ffcc1ee3881f78524d3f5272944ab339d5ee647 100644 GIT binary patch delta 1066 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 delta 1273 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 Tus@$0_?-UwbTj#plz3x z5Y_NrIn6@U!I35!+*s!SY9`8lV^mPvk3}REyQX6n+tDu;Gq7YXu1dMElvrA89#u>I zPUD0*MJM0voWfI8qXy2a>2jPRAct&yoMn~7_oTDyAeg_og}&|a#d^IA9s_=wS>hCx z94Via4uF(zt^&Z27|aEbN5+Z5iC~a~0)xo;5I_J?cT_MSiv|TtdQB!PjRK)T(v|n)I(!f}Y0i=r``LO%QOcWW_07Z|0C0m^8A8 zg_86#C2&gsVElP;lI>o0384DDW40pjuq^u#KuN}zwA5p}+y7j&5@K>x+G3#0rWgGl zlCLg_n<6`b(W74hu*}=Vw>sT1R8q^>?i_BClvQQGwBri!1`gS;i&ScNL|T`OSNHVg z^J}0|d{!~YZookzO2ccMFx6e6l^lXT<*&fH6@izmm3A9fJv7o@i$iK4q~MTjck6~5-88*6Zw`H++_pOnEP^Bb70LJ(DI(9ZzH(Zw zDHTD32eL3922KT$jO=P0th3Y1WH))wI_gjOM(@(a-Y_4Qe7Z;wwB-fDpqrBa77(Rg zH0;B-IKY)UTfXv%0OlZXut-vJS+p;w2~f$qZ+eKr74eTHE|oy@t{Y7gajCcew(% zc|hOKa(x+FoKE{gpX<6huz_mc1M9-(=Y@;XC)dQjQ%beze+nlpBbT~zQs-HsB@T_Q zghq{(^mB+nbhwQ{2JRAiiAWfG>M_c4{=l;LK-dm}h;`WRqf+?21=gDda+?UJoy2gY zKc4_FLyVCy2syh7E;5^ouG0+Ca0kbS!B0TW-ky+I!duBRp(St(_(2P&V%v}4w9j2UquZWfE z-QLMtFT$N5ppbwQd6(?BioK!=3_gkPJ+cBhs=+-?GKI~H=olVKjJOx-hFv+M^D+}- z{!Xup{_=J>QVVnMGly3YofC4T`y0?XHP^MY zD8JPt7Hd|+kD>}RC^#R$lT#S(#=VuaJ63&iiV{7|KFY^}o2 z&+R?@m*++Ubu1XIe>(=A!5!bfESNYMnR{yv3u`m^%9qAG8xWjih1dk!|L!uUXLFo- zLDXR)(hZmSNSI=4+j)J{8!|DR`PNe2K^=271VLg{?`$uUP(URyIvO%k@*;d*-J9Jh z0C3R6T_OKCEx6`eM`c1U-M0UXaI^MZ=w-b8p1_$d_hgU2eVxH*^6+Ka`+`BDM{iOH zftgtcb5k}>^hf`ufwgFK#-HonSE`Mr+8-}#;@8wt&ur^JdYMK_j(iX4$>&PHki~if z0FCdbPW!Qb^iJD$70qZ@P$$F)1$(0p*1yu1_+nK5m7L~X;DklMhsK)879zz__$cNP z7W%B|OWm(C@loSIBIR3wraeENzSzN{q6w*Z3#kjFRHUc2Ok&SnkFwv=v)G&lR!*0$ zAtX>Q`m&QD_aus_pqV()Lng5!WW|?;OA$33K$glDqu#oR39WN+C(Rd((dcmMz(C$V zbv>ZE&w?Dh9Kzow-7>6CvwVCf_qdnoKX_B`lq-Y4v2iSvgT!e$%B|5mqU7c+i*Irt zYKwNsq>D3fWZ@(KrgX>b$PcUd&WOWJ!Ogc$qmKhmKzkno{R-3=9nqcc957jL50u7# zUFtZcHts-(ezAHcdM;`nkxG1pKg!gRo8V_4v*e#WA$7!nTa{IIKfSON^DQyLkimho zBngGYvr>|+B;kRB2FjMc@AJG9Iochmrg}moI9pGjF1D@0`q-;>R9+H^a?6+=?yz+A z=c3%e2RC5vtv#~eXR09=f8h7F*}XA}X7Lb{px!~eB>stZybpXcOk;!SZ|;5Z{67&~ zG}1ET0MOL)no^h7Bs|p?dNnaJ8|j)hS8ih;@USCG?;)jm%_ZG zJ55_(=x#5&v9PB_fzjFc2x$21>nLff_suc2j2j}7jQM5tQvCh6G;&vw^@!kBApAOm zY-_(ZYre_B+NOHI_SNa7A`bkrl9=39Nx|qaAi}WjOalvGtdtNjCL*6Bvqke>fNF<_ zkt?9=H^rCz!IY%&o5CnyuBu<#466iOi0D2kNDjTA+2)ubl?vE@zTg?=(om&WfN=@iVicvjeMAlLjnO^kpo)3)Z^)N1tsTK@6$ zs7;fp9&&tM7x9eyK4JZvkHRNe6};+ky%!sA!OP8tUt=O@uH~3HH3BKm)}R)9r_RFn zqVc@T?|YiqL<@Q@!w6kKE?{Ovq;$Ac_w9TOknKLHaXhf z)q6*d>+w#@ppD6p@WJ0HJ<;5Z_HH-XZu+t|G{zts4H_VeN)`ToYZVr|OzEJFC^bG& zA8r#g&g7JqbUz=c`!Qf5;hW>?OO~u40{7Kip>V5hl<9rK|+FFWaupYauD5gsj4=lD1vqjA1_X0xb}TIESu`^FS&mvF!q6ym?oXG7uk%#o4}gU~Vdoz7I7JsJMSg6kJ^$ z7U_VH_&6U1;I{anvgZ?wkQ$Bi2-s82JppA%?1n4v9p1Mf2C4;*3E^D51OOZOr#@ME zElpG<{jd_gyhrD)*@rr{$Hif&#uKjD2vgKec4WLi3mEDqZfO^&sJ1s9pd)Z*^44e8MMgt3R_>Z94466wC&7w@{I6P6#;H4f4 z)WH4|K`pn&XR8+RsN|VoL@i{uq2y+Gi>&qq?U72Ry5F!Jwtf7_G=N<{NZo5LX3r|pa1lb*x zt-6Vth>o(KD4Hd6N5?Ofe7LoQpAo9?51c9=+JqJn8LX zhPPRiqI5BC_!WV?^ZPQk=Z195tUIpL+46+uNNaY)M(gmJh-%(f7U1&XTe%A6B zwVSpk_a5KXeJ^Q(|Fnb=@JH=fj6=4a>s{W{1i9<=sc@l{u&?UM7VUL1wGD*gJ!5>WYIcLJwu$4l57qY7<%~|F0i^CoL=d z2T&E$`o2;Z zxkA}?P;63ulio4hD`DlQCU~tkBz|&R0%-xjB80_`w0z$?{3cP7m}gHTH@X;Ax)m2X=(vKIpthLeKwdFBJsdbv-0n!l&6$zsp;}tLKwGvjR{yC77{6J#! zAv(APQRAC0#^m36v6u+<$Au(BrRIJE6#$M}1G6Gz7)>rJv05<~>v4E6o2hL7d%!wV z^0$FWILA67hVeeg+wZG6gGi>MkR%=HgAz|fy%D-@`c1Eqv;+5QIr%+)yYe07Bl0@E zH=}67AF+m~9L9N*kNr@eE^m(?Cz@D+euZ0#vI;o!PY6~|ztnA>&ef{X#+h10&1nS2 zbzY)r;vVI(3rTB23@w4RvCj>b2Mkz;b0}kQpdb|LYkpA=Es`5E$bZy@pYmm>irg6= zU$zaq`trh9ND-%UvDbR+4j~(>1FwBK zDC!bDvO!5MY59$wVmvsW(`Wb^k?y@-4cm^feb+?^t_q-nJS!H$!c$*^@othug+4-3 zVUrGXYcPfeY29Q{=oXEC25*cm#K#mhjePgryS+0qTsCk=9mzm?@5+0(itIj=Ta;xc8=@a;Vc z)(;h&Lq>?V=8ICGUz8evm0$6z-I!e%YTkdaheC^=i;E+Y-N8z~nzvW7ISs!SvmM$@ zXRJblXAM$D&fZQIaME_h-l`fB4B% zC`2fq=oD!2uNBJj_CHvcNp24X*;Fc%Z*d;B>Jp@}Wvu0$)(uD_r-}qct{vW8v3Igt&@kcocsbqsTPZ8avi4#%`Od)1Yhr zr8k(J+Ke1gi7f2Wj>e~Uo=D}s;>!gQ^SVc(Z>KA zl3;Dn`M2xch80UqY)H#inPNel8BJ(vO~Fyg@O`$|L&RaZAOci%jKCZiP$`sr?)88wsi>@TTqGIGBQQwoIV#T ztX0e?t@#qevuTq%-|}Z~#m9aUpF$cByDML+XbDWVWrJ*mE&9=0Uu<8NwX#oyt#_qd zs|ik8i!ozrLpE&GvC16zI^}iG7N6G) zgZtxzrw2GJZCk76TL|35_l=i5^OarFe(!*Q^WqB$*me+gW_NnP*j>gCM7yNK8eNXn zww;YKpEI+T??eG=RcoxQ2vu)eh;Wm%9g)k!>O+zB=r};OlMo+>EIF%clIzO9GBc1U{TJ`h?BSokEba~Kw*~)aRLUkvrms*PL z?v^;$Xl<(FcFKP7v{<6yI;+#)t`v2BPQfSE6VG$i2DZnl%bCYy$3uG^6XAP2;ewTi z>t6x>b?3SG)u`I%T5M>B-kBiWJ6|lI!cu(-e5s+-hp8+87c}vqPHgnvzuy1UrO-d? z;;nLE--~LEomZ}(2v}4i2=}B1vb?1olf{#p%HA{?d&~c_K9}hzTQWtXaO1~c9(KHv z1wN%$yX@sS-FVAZJ8}t?7`@S)>*vI&o$baegeTfqu zb5YvL1|HrthCK$;s;dlh^*Seoqp(l5^u#kCK9RV8lY@F^Nf+MKH-Ji>r2f*RoIxo5 ze>P|4@x}gz*Kf8{Kk#!C@Imhij&B+LO_Yfp9cym`h|;K=GA^EfhOc(q8d&tkQ)1Bj z4C}$IGXHd1Wwj^dfF`>)M;fHjIvGeyl^GV#N$>!tepD_&a3O88Uq!}P@dI-fNBR52 zOAZc}qfe%MAF^oirkU;Kc788zm(O}IBIs^xLGH)KhB$TayVQB^WgH~zYGe*9z!JNy zz|kh$TusLG==o6mCH*tqqe?pjqf?Cy<6$O_h?-jq z?Facke{t4A4RouG?k!bZGCC_O=L6*4ZpqlQVMgL2ZQ$l=0J_L1DYE-_zaNKnq_PWY z7@y@*4jPgeB8ePxYv}R)3c(-X-|1*WiP)Q@M4~q;%Ak0{KMi!I&o5q$g1yl|YmIf) z*cNs5XFpdy+?_xtm#a~$xOOI0BQ;E_f`)J#`M*&sga!yX70-1$!KtRc43dSl%_eG1 zt%fEfFH|1lp9F6Vp-VDjcU8&B?eeE=*S;kGeJVn~%Kj{@TNUJ#w&8^OX>r<$wuqU` z*nS-9Wm*YPZH;%W>ORT}hJ@V4CuTe_|FEBrjOtdJ;!Vd3Qp&O^zT@RZ=MX$>U|{N4 zuGX9kvDXVba?IlmmPl=h>Pg?Kuv~H-h5^_3bX3ibMCI538bG%3@$Vk=X z)r}VVO-sK{O2whAYEEPp_{Wm5HR$_PJpRDwrR`ib<=?yJwz4)Fi?NVU^y<~WSq_eXyAQ+MGps#`lKWz1uZ;d=jsZ4k}aTT*-;v}}LTbzE{b-!%jdnNyCB z*yCW4UyWuY6f|60-s7M7D%{-k%65x2oE~VY_cghHbKbA3?}MGaDw)5XPhBAU+bszw zrO!LphN;-UbTRz$E8>o-3gTkuHr%N3Tx^aGJVT?SC&~gSE>tV8Rko^QD}qHbxVh?r zdrcFTC(n1x^1P(oof6ZnJ6x9C+G!1&U20ba^EMjnX|-6}CXR|p#{k(FHSdz%!mSYE z%~8Nc#~L;|?196$@7OL!=7(q{_oV!LCE@;K$8>&KssY=m0alvwXEclhjoyV~KrMPo zGj%&=gf-vUAFNkw;qH@s!vh=3@D%2MJ6k6S{GA<{r_bjqx z(SAvVEa&C`wR1mROzBr#CvL1*N6Fh1DGC|&WEchV2y#I;z90v2eV?T`0K=aXc$hv{ ziL_+plt~6H9CIg_Oub{V2Hb2ONUs?Qh5L^I{=4kO9rNFIF@ef0&rMV_dy-rfjK1iR zpM%{$F>2A*_2km2kx+1GizK*!s1TmLujMQ>R9isenAv!Ab#nu8inh3l$bj&0Y3&Jl^GKTRfoVUJ-~s&vetxJnzih2Q+6>#j~_ZQ&c@vn#Xn6ID#?tinMrNoPXX zl00Wud#=>n(x|8F3IXwOS^0CgOYMP6_YJ>_{i(3CaI}*&do0RV_FQpbXe`X7^Wmjj96i6mOF>a0#Ml5`ZIipPOZ+qG zlK`erRHt-^@ zGiPU~u$RJ(K{hGt+(gwVh1wf85R-v6`H`;CY(m*qwWQtdTgVTq?jM>&vgoi*Bh9n} z+#O{7mq@;8J-Ec~*8O+s=&+dx8H%Jz~N<8-*9b$K|sF@d4 z1^DIyY9u4}cD&x#E!x}0rsvYX*6yvEu}!aM1sG4^R$v%gZJoZ+1?T$g%1w zXO|kX_{zY(2{heSVm>@W6X_#(3#|p|4E?;)v*9_n4k+V$&o#Er$al|fIP7}*QHAUd z=D*jf>@dDzcQXvOo^(r%P-uNNNKT6urK>Q&6Kf`FY~{1n0z9S9d@AO_4rq=L)ncmK zQtD!HxMYY~u#B>*>YnP>pJe6`*A$LpK+^4?gcVYrQmxNESf?2!_=fD%#p<3+eq=;S zYHQRoW9W0kXtLT&1>nj?DbRvd+}jj2ym`imB=m2h{PxcxI*B_;Nx;kAHkWL#{ zSiN?e0{#1OL29^$(t|Yxa)LmRQFA^BUEtk1>$1K>i-2;0TVG^%uHg_j@)orACDp)V;BIHfr_8rXyXrsO$$d1i4O0YOO^(``6BS7J&(2E6_k z>g@9BsDTHS0LnIF=5jFc!L+Ko_-BTU8;lt7%+#zXsH>QqB=w$vRbzo^Yb!|#mwxvo zRB&!z*PZm;$BTUwRV(I494IgWWC+J1XAm5yKx;ho|tbYqO=g~sCOQqsvo@-l&9fGZXxh!WY zqchaJlW`M`%|{`PNKPi%s^>K zy8J@>m*wAJXUsB_L{CkK8)%nk6`-5u7rzdF;cCT-)=5F=wXqCYtBAeIR z$%M^yzl84Jj7aD|Ez zoULA0tOItYa5v2`*S1kZqS|70{4()l{sg)Yeo~^XTS-29_F*iejYTgttSW5YRcFg( zCz;f6V`4?UoXw|=nN0d<9xiQXy;$UprHMfUZHsQ+AoeEoL{E1$%{LE@>XH8E3d^{Tu|0dJKspaTbF@7|!)N8@V%4ir{6U*$SaO)4`ztKX4syuMLJ0^x zniAMVI1t@OeSAQpF|u8?h*@P>Y4zicraWN= z+$fZbar7940C24wsI_4ykFZgs?o9-)u)SgeR)qnD+p`#w9N}tmow*6e^dL1j;GkjG z{grM{NW1eH>14HHW^!d|PvkS)jR}WucPgeTk0MQV$ZAj+>9EchKfV9~_VO%i2u9F* zG+Wx6A79D7>U9!neA#97CLVlB>M0na*yaLNi4={!d(!>wSL$k%mn~KG!@qZ{$eG%Th zVQ<~K(V`c11<}N}VP2fr!kxX(cO1h_Wm_&OUDwj!BZM74c_@y;>DQwffyuottWKru z0fMUIw`!UIsX1(6eA(U$xpu!PM`NC%A~~m?;w}<_&pH~JnBMf+Ex^?%<{S-U?;^#B z>e{4vCD;kl)`-`-tDYIRN%E(J$=H>lMZVs>h9V5ANa$+3hjbN9K(oKtJvVtchR{8O?~_EZI#$xe7^9ipsWhR+NYJW9mBmUuT1i8+N7qWI=fv}LAX-s zlG?e5i(VuI^<-!vvC!d+{Wg`={5ON(GV?yl{G7dmRv;-UyY`^MuokKz++$7Zn(6AF z67BJe!D3I78DLG{r1H&}KO4e;_Ze01K^sAI^f>up7J<@T+UiajeH}g5#)6cOPB*nV z+!m%47c+ao9MsB%P_rCQK0!|7%rpEckPF(k1>7`-*gZaGGfDLSMnicjeNV3cS z=W^W?jnZxL&xO*^5K#Q8b(wv)bp&L)R)4kP^`XNUDTu!^y6AvKy(0WaCY;3G1?~>> zAE?Uz0p;{lg(*pNQ9tf(1KF_ut(j=!2kE|ga3Qp`0>-N1VDu-%@@K~P}W!fP4*0UvB-Ly_OsHtDLJj*v&* zL}BgZ^>$Wh{J8x{lSR_)5-&o6BcuV0XZ8Zuzs%;&Ch117P|giP2oZ zP|d5Nh3!-oa96?nnj^%s1jeG`9}5T!aslQ+Sj-HAM6-n9oX+gSbWIRpn2ZH&mJkL> zW(m7Ioq1q-wf+kP1_ddzz4Fw{2mD#qA$;Y@Jdp=*zDIctWtUR-0anew~&S5a;l==0B4PHM*t|F?F3yi{NNI@cZ!?B077=9mge!fm?2 z=4hJjg9_k|m_f{ZNnWbqGMHS6eC3w!LW>{!eA^Z1+#LC?_s6`#sULY{L7oWos)MCo zi=XUusiqphd}tabe&T@Fa2Z1WmTH7a?2B^WFmnUvUltZx09FKe28i$`3zR$u+sA7B z!boO_>|s-e7Ze1$b_1+iiLl|3IeJxe{V$2o{`X~V|8K-pC?JzhY}RYrkOL;?1IS7$ KNmPlMg!~Ufuo9>M diff --git a/SandboxiePlus/SandMan/Resources/SandMan.qrc b/SandboxiePlus/SandMan/Resources/SandMan.qrc index f56f6ff768..f22b1aff4a 100644 --- a/SandboxiePlus/SandMan/Resources/SandMan.qrc +++ b/SandboxiePlus/SandMan/Resources/SandMan.qrc @@ -25,6 +25,7 @@ Actions/Stop.png Actions/Advanced.png Actions/Service.png + Actions/config.png Boxes/sandbox-b-empty.png diff --git a/SandboxiePlus/SandMan/Resources/SandMan2.png b/SandboxiePlus/SandMan/Resources/SandMan2.png index baf2b6f3477422a2cecd2a2efbba0285e009dfab..75a9e15c84fb525c5379f5ce3254284cb6e98345 100644 GIT binary patch literal 5698 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 P%WzA_Si4lyKIH!ZY5h4| literal 8164 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 diff --git a/SandboxiePlus/SandMan/SandMan.cpp b/SandboxiePlus/SandMan/SandMan.cpp index b825a9ab34..c2056ec668 100644 --- a/SandboxiePlus/SandMan/SandMan.cpp +++ b/SandboxiePlus/SandMan/SandMan.cpp @@ -10,6 +10,7 @@ #include "./Dialogs/MultiErrorDialog.h" #include "../QSbieAPI/SbieUtils.h" #include "../QSbieAPI/Sandboxie/BoxBorder.h" +#include "Windows/SettingsWindow.h" CSbiePlusAPI* theAPI = NULL; @@ -81,6 +82,13 @@ CSandMan::CSandMan(QWidget *parent) theGUI = this; + m_DefaultStyle = QApplication::style()->objectName(); + m_DefaultPalett = QApplication::palette(); + + LoadLanguage(); + if (theConf->GetBool("Options/DarkTheme", false)) + SetDarkTheme(true); + m_bExit = false; theAPI = new CSbiePlusAPI(this); @@ -114,47 +122,6 @@ CSandMan::CSandMan(QWidget *parent) m_pPanelSplitter->setOrientation(Qt::Horizontal); m_pLogSplitter->addWidget(m_pPanelSplitter); - /* - // Box Tree - m_pBoxModel = new CSbieModel(); - m_pBoxModel->SetTree(true); - m_pBoxModel->SetUseIcons(true); - - m_pSortProxy = new CSortFilterProxyModel(false, this); - m_pSortProxy->setSortRole(Qt::EditRole); - m_pSortProxy->setSourceModel(m_pBoxModel); - m_pSortProxy->setDynamicSortFilter(true); - - m_pBoxTree = new QTreeViewEx(); - //m_pBoxTree->setItemDelegate(theGUI->GetItemDelegate()); - - m_pBoxTree->setModel(m_pSortProxy); - - m_pBoxTree->setSelectionMode(QAbstractItemView::ExtendedSelection); -#ifdef WIN32 - QStyle* pStyle = QStyleFactory::create("windows"); - m_pBoxTree->setStyle(pStyle); -#endif - m_pBoxTree->setSortingEnabled(true); - - m_pBoxTree->setContextMenuPolicy(Qt::CustomContextMenu); - connect(m_pBoxTree, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(OnMenu(const QPoint &))); - - //connect(theGUI, SIGNAL(ReloadPanels()), m_pWindowModel, SLOT(Clear())); - - m_pBoxTree->setColumnReset(2); - connect(m_pBoxTree, SIGNAL(ResetColumns()), this, SLOT(OnResetColumns())); - connect(m_pBoxTree, SIGNAL(ColumnChanged(int, bool)), this, SLOT(OnColumnsChanged())); - - //m_pSplitter->addWidget(CFinder::AddFinder(m_pBoxTree, m_pSortProxy)); - //m_pSplitter->setCollapsible(0, false); - // - - //connect(m_pBoxTree, SIGNAL(clicked(const QModelIndex&)), this, SLOT(OnItemSelected(const QModelIndex&))); - //connect(m_pBoxTree->selectionModel(), SIGNAL(currentChanged(QModelIndex, QModelIndex)), this, SLOT(OnItemSelected(QModelIndex))); - - m_pPanelSplitter->addWidget(m_pBoxTree); - */ m_pBoxView = new CSbieView(); m_pPanelSplitter->addWidget(m_pBoxView); @@ -246,6 +213,8 @@ CSandMan::CSandMan(QWidget *parent) m_pKeepTerminated->setCheckable(true); m_pMenuOptions = menuBar()->addMenu(tr("&Options")); + m_pMenuSettings = m_pMenuOptions->addAction(MakeActionIcon(":/Actions/Settings"), tr("Global Settings"), this, SLOT(OnSettings())); + m_pMenuOptions->addSeparator(); m_pEditIni = m_pMenuOptions->addAction(QIcon(":/Actions/EditIni"), tr("Edit ini file"), this, SLOT(OnEditIni())); m_pReloadIni = m_pMenuOptions->addAction(QIcon(":/Actions/ReloadIni"), tr("Reload ini file"), this, SLOT(OnReloadIni())); m_pMenuOptions->addSeparator(); @@ -264,6 +233,8 @@ CSandMan::CSandMan(QWidget *parent) m_pAbout = m_pMenuHelp->addAction(QIcon(":/SandMan.png"), tr("About Sandboxie-Plus"), this, SLOT(OnAbout())); + m_pToolBar->addAction(m_pMenuSettings); + m_pToolBar->addSeparator(); //m_pToolBar->addAction(m_pMenuNew); //m_pToolBar->addAction(m_pMenuEmptyAll); @@ -341,18 +312,22 @@ CSandMan::CSandMan(QWidget *parent) if (theConf->GetBool("Options/NoStatusBar", false)) statusBar()->hide(); - else if (theConf->GetBool("Options/NoSizeGrip", false)) - statusBar()->setSizeGripEnabled(false); + //else if (theConf->GetBool("Options/NoSizeGrip", false)) + // statusBar()->setSizeGripEnabled(false); m_pKeepTerminated->setChecked(theConf->GetBool("Options/KeepTerminated")); m_pProgressDialog = new CProgressDialog("Maintenance operation progress...", this); m_pProgressDialog->setWindowModality(Qt::ApplicationModal); + if (!bAutoRun) + show(); + if (CSbieUtils::IsRunning(CSbieUtils::eAll) || theConf->GetBool("Options/StartIfStopped", true)) ConnectSbie(); connect(theAPI, SIGNAL(LogMessage(const QString&, bool)), this, SLOT(OnLogMessage(const QString&, bool))); + connect(theAPI, SIGNAL(NotAuthorized(bool, bool&)), this, SLOT(OnNotAuthorized(bool, bool&)), Qt::DirectConnection); m_uTimerID = startTimer(250); } @@ -395,6 +370,9 @@ void CSandMan::closeEvent(QCloseEvent *e) { hide(); + if (theAPI->GetGlobalSettings()->GetBool("ForgetPassword", false)) + theAPI->ClearPassword(); + e->ignore(); return; } @@ -486,8 +464,6 @@ void CSandMan::timerEvent(QTimerEvent* pEvent) theAPI->UpdateProcesses(m_pKeepTerminated->isChecked()); } - m_pBoxView->Refresh(); - if (m_bIconEmpty != (theAPI->TotalProcesses() == 0)) { m_bIconEmpty = (theAPI->TotalProcesses() == 0); @@ -496,16 +472,10 @@ void CSandMan::timerEvent(QTimerEvent* pEvent) m_pTrayIcon->setIcon(Icon); } - /*QList Added = m_pBoxModel->Sync(theAPI->GetAllBoxes()); + if (!isVisible() || windowState().testFlag(Qt::WindowMinimized)) + return; - if (m_pBoxModel->IsTree()) - { - QTimer::singleShot(100, this, [this, Added]() { - foreach(const QVariant ID, Added) { - m_pBoxTree->expand(m_pSortProxy->mapFromSource(m_pBoxModel->FindIndex(ID))); - } - }); - }*/ + m_pBoxView->Refresh(); OnSelectionChanged(); } @@ -605,7 +575,7 @@ void CSandMan::OnLogMessage(const QString& Message, bool bNotify) if (bNotify) { - int iNotify = theConf->GetInt("Options/Notifications", 1); + int iNotify = theConf->GetInt("Options/ShowNotifications", 1); if (iNotify & 1) m_pTrayIcon->showMessage("Sandboxie-Plus", Message); if (iNotify & 2) @@ -613,25 +583,36 @@ void CSandMan::OnLogMessage(const QString& Message, bool bNotify) } } -/* -void CSandMan::OnResetColumns() +void CSandMan::OnNotAuthorized(bool bLoginRequired, bool& bRetry) { - for (int i = 0; i < m_pBoxModel->columnCount(); i++) - m_pBoxTree->SetColumnHidden(i, false); -} + if (!bLoginRequired) + { + QMessageBox::warning(this, "Sandboxie-Plus", tr("Only Administrators can change the config.")); + return; + } -void CSandMan::OnColumnsChanged() -{ - m_pBoxModel->Sync(theAPI->GetAllBoxes()); + static bool LoginOpen = false; + if (LoginOpen) + return; + LoginOpen = true; + for (;;) + { + QString Value = QInputDialog::getText(this, "Sandboxie-Plus", tr("Please enter the configuration password."), QLineEdit::Password); + if (Value.isEmpty()) + break; + SB_STATUS Status = theAPI->UnlockConfig(Value); + if (!Status.IsError()) { + bRetry = true; + break; + } + QMessageBox::warning(this, "Sandboxie-Plus", tr("Login Failed: %1").arg(Status.GetText())); + } + LoginOpen = false; } -void CSandMan::OnMenu(const QPoint& Point) -{ -}*/ - void CSandMan::OnNewBox() { - QString Value = QInputDialog::getText(this, "Sandboxie-Plus", "Please enter a name for the new Sandbox.", QLineEdit::Normal, "NewBox"); + QString Value = QInputDialog::getText(this, "Sandboxie-Plus", tr("Please enter a name for the new Sandbox."), QLineEdit::Normal, "NewBox"); if (Value.isEmpty()) return; theAPI->CreateBox(Value); @@ -784,6 +765,23 @@ void CSandMan::OnSetKeep() theAPI->UpdateProcesses(false); } +void CSandMan::OnSettings() +{ + CSettingsWindow* pSettingsWindow = new CSettingsWindow(this); + connect(pSettingsWindow, SIGNAL(OptionsChanged()), this, SLOT(UpdateSettings())); + pSettingsWindow->show(); +} + +void CSandMan::UpdateSettings() +{ + SetDarkTheme(theConf->GetBool("Options/DarkTheme", false)); + + if (theConf->GetBool("Options/ShowSysTray", true)) + m_pTrayIcon->show(); + else + m_pTrayIcon->hide(); +} + void CSandMan::OnEditIni() { if (theConf->GetBool("Options/NoEditInfo", true)) @@ -901,7 +899,12 @@ void CSandMan::OnSysTray(QSystemTrayIcon::ActivationReason Reason) { if(TriggerSet) NullifyTrigger = true; + hide(); + + if (theAPI->GetGlobalSettings()->GetBool("ForgetPassword", false)) + theAPI->ClearPassword(); + break; } show(); @@ -976,6 +979,62 @@ void CSandMan::OnAbout() QDesktopServices::openUrl(QUrl("https://www.patreon.com/DavidXanatos")); } +void CSandMan::SetDarkTheme(bool bDark) +{ + if (bDark) + { + QApplication::setStyle(QStyleFactory::create("Fusion")); + QPalette palette; + palette.setColor(QPalette::Window, QColor(53, 53, 53)); + palette.setColor(QPalette::WindowText, Qt::white); + palette.setColor(QPalette::Base, QColor(25, 25, 25)); + palette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); + palette.setColor(QPalette::ToolTipBase, Qt::white); + palette.setColor(QPalette::ToolTipText, Qt::white); + palette.setColor(QPalette::Text, Qt::white); + palette.setColor(QPalette::Button, QColor(53, 53, 53)); + palette.setColor(QPalette::ButtonText, Qt::white); + palette.setColor(QPalette::BrightText, Qt::red); + palette.setColor(QPalette::Link, QColor(218, 130, 42)); + palette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + palette.setColor(QPalette::HighlightedText, Qt::black); + QApplication::setPalette(palette); + } + else + { + QApplication::setStyle(QStyleFactory::create(m_DefaultStyle)); + QApplication::setPalette(m_DefaultPalett); + } + + CTreeItemModel::SetDarkMode(bDark); + CListItemModel::SetDarkMode(bDark); +} + +void CSandMan::LoadLanguage() +{ + qApp->removeTranslator(&m_Translator); + m_Translation.clear(); + + QString Lang = theConf->GetString("Options/Language"); + if (!Lang.isEmpty()) + { + QString LangAux = Lang; // Short version as fallback + LangAux.truncate(LangAux.lastIndexOf('_')); + + QString LangPath = QApplication::applicationDirPath() + "/translations/taskexplorer_"; + bool bAux = false; + if (QFile::exists(LangPath + Lang + ".qm") || (bAux = QFile::exists(LangPath + LangAux + ".qm"))) + { + QFile File(LangPath + (bAux ? LangAux : Lang) + ".qm"); + File.open(QFile::ReadOnly); + m_Translation = File.readAll(); + } + + if (!m_Translation.isEmpty() && m_Translator.load((const uchar*)m_Translation.data(), m_Translation.size())) + qApp->installTranslator(&m_Translator); + } +} + ////////////////////////////////////////////////////////////////////////////////////////// // /* diff --git a/SandboxiePlus/SandMan/SandMan.h b/SandboxiePlus/SandMan/SandMan.h index f220ede9c3..619d113f3c 100644 --- a/SandboxiePlus/SandMan/SandMan.h +++ b/SandboxiePlus/SandMan/SandMan.h @@ -8,10 +8,11 @@ #include "../MiscHelpers/Common/ProgressDialog.h" #include "Models/ResMonModel.h" #include "Models/ApiMonModel.h" +#include #define VERSION_MJR 0 #define VERSION_MIN 3 -#define VERSION_REV 0 +#define VERSION_REV 5 #define VERSION_UPD 0 @@ -60,13 +61,13 @@ public slots: void OnStatusChanged(); void OnLogMessage(const QString& Message, bool bNotify = false); + void OnNotAuthorized(bool bLoginRequired, bool& bRetry); + + void UpdateSettings(); + private slots: void OnSelectionChanged(); - //void OnResetColumns(); - //void OnColumnsChanged(); - //void OnMenu(const QPoint& Point); - void OnMenuHover(QAction* action); void OnNewBox(); @@ -76,6 +77,7 @@ private slots: void OnCleanUp(); void OnSetKeep(); + void OnSettings(); void OnEditIni(); void OnReloadIni(); void OnSetMonitoring(); @@ -96,9 +98,6 @@ private slots: QSplitter* m_pLogSplitter; - //QTreeViewEx* m_pBoxTree; - //CSbieModel* m_pBoxModel; - //QSortFilterProxyModel* m_pSortProxy; CSbieView* m_pBoxView; @@ -139,6 +138,7 @@ private slots: QAction* m_pKeepTerminated; QMenu* m_pMenuOptions; + QAction* m_pMenuSettings; QAction* m_pEditIni; QAction* m_pReloadIni; QAction* m_pEnableMonitoring; @@ -156,6 +156,14 @@ private slots: bool m_bExit; CProgressDialog* m_pProgressDialog; + + void SetDarkTheme(bool bDark); + QString m_DefaultStyle; + QPalette m_DefaultPalett; + + void LoadLanguage(); + QTranslator m_Translator; + QByteArray m_Translation; }; extern CSandMan* theGUI; \ No newline at end of file diff --git a/SandboxiePlus/SandMan/SandMan.vcxproj b/SandboxiePlus/SandMan/SandMan.vcxproj index 232e5294ee..5fc75e951c 100644 --- a/SandboxiePlus/SandMan/SandMan.vcxproj +++ b/SandboxiePlus/SandMan/SandMan.vcxproj @@ -193,7 +193,9 @@ + + @@ -205,14 +207,20 @@ Create + + + + + + @@ -226,6 +234,10 @@ + + + + diff --git a/SandboxiePlus/SandMan/SandMan.vcxproj.filters b/SandboxiePlus/SandMan/SandMan.vcxproj.filters index a237d55736..7335e4f919 100644 --- a/SandboxiePlus/SandMan/SandMan.vcxproj.filters +++ b/SandboxiePlus/SandMan/SandMan.vcxproj.filters @@ -35,6 +35,12 @@ {6accf3ae-da17-4c0f-ba83-214e3874b029} + + {20d5954b-be86-4a34-948d-00954dcfd07b} + + + {d0068730-c9fb-49ef-90ec-8ecd2131ae47} + @@ -64,6 +70,18 @@ SandMan + + Models + + + Helpers + + + Windows + + + Windows + @@ -72,6 +90,9 @@ Resource Files + + Helpers + @@ -95,6 +116,15 @@ SandMan + + Models + + + Windows + + + Windows + @@ -111,4 +141,12 @@ Resource Files + + + Form Files + + + Form Files + + \ No newline at end of file diff --git a/SandboxiePlus/SandMan/SbiePlusAPI.cpp b/SandboxiePlus/SandMan/SbiePlusAPI.cpp index f49c016b3d..c2100fd31a 100644 --- a/SandboxiePlus/SandMan/SbiePlusAPI.cpp +++ b/SandboxiePlus/SandMan/SbiePlusAPI.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "SbiePlusAPI.h" +#include "..\MiscHelpers\Common\Common.h" CSbiePlusAPI::CSbiePlusAPI(QObject* parent) : CSbieAPI(parent) @@ -120,14 +121,14 @@ void CSandBoxPlus::SetLogApi(bool bEnable) void CSandBoxPlus::SetINetBlock(bool bEnable) { if (bEnable) - DelValue("ClosedFilePath", "InternetAccessDevices"); + DelValue("ClosedFilePath", "!,InternetAccessDevices"); else InsertText("ClosedFilePath", "InternetAccessDevices"); } void CSandBoxPlus::SetAllowShares(bool bEnable) { - SetBool("BlockNetworkFiles", bEnable); + SetBool("BlockNetworkFiles", !bEnable); } void CSandBoxPlus::SetDropRights(bool bEnable) diff --git a/SandboxiePlus/SandMan/Views/SbieView.cpp b/SandboxiePlus/SandMan/Views/SbieView.cpp index 65e93c20cd..0be4a0f883 100644 --- a/SandboxiePlus/SandMan/Views/SbieView.cpp +++ b/SandboxiePlus/SandMan/Views/SbieView.cpp @@ -4,6 +4,7 @@ #include "../QSbieAPI/SbieAPI.h" #include "../../MiscHelpers/Common/SortFilterProxyModel.h" #include "../../MiscHelpers/Common/Settings.h" +#include "../Windows/OptionsWindow.h" CSbieView::CSbieView(QWidget* parent) : CPanelView(parent) { @@ -32,6 +33,7 @@ CSbieView::CSbieView(QWidget* parent) : CPanelView(parent) m_pSbieTree->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_pSbieTree, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(OnMenu(const QPoint &))); + connect(m_pSbieTree, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(OnDoubleClicked(const QModelIndex&))); connect(m_pSbieTree->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(ProcessSelection(QItemSelection, QItemSelection))); //connect(theGUI, SIGNAL(ReloadPanels()), m_pSbieModel, SLOT(Clear())); @@ -62,7 +64,7 @@ CSbieView::CSbieView(QWidget* parent) : CPanelView(parent) m_pMenuPresetsShares->setCheckable(true); m_pMenuPresetsNoAdmin = m_pMenuPresets->addAction(tr("Drop Admin Rights"), this, SLOT(OnSandBoxAction())); m_pMenuPresetsNoAdmin->setCheckable(true); - + m_pMenuOptions = m_pMenu->addAction(tr("Sandbox Options"), this, SLOT(OnSandBoxAction())); m_pMenuRename = m_pMenu->addAction(tr("Rename Sandbox"), this, SLOT(OnSandBoxAction())); m_pMenuRemove = m_pMenu->addAction(tr("Remove Sandbox"), this, SLOT(OnSandBoxAction())); m_iMenuBox = m_pMenu->actions().count(); @@ -168,6 +170,7 @@ void CSbieView::OnMenu(const QPoint& Point) m_pMenuPresetsShares->setChecked(pBox && pBox.objectCast()->HasSharesAccess()); m_pMenuPresetsNoAdmin->setChecked(pBox && pBox.objectCast()->IsDropRights()); + m_pMenuOptions->setEnabled(iSandBoxeCount == 1); for (int i = m_iMenuBox; i < m_iMenuProc; i++) MenuActions[i]->setVisible(iProcessCount > 0 && iSandBoxeCount == 0); @@ -207,11 +210,15 @@ void CSbieView::OnSandBoxAction() SandBoxes.first().objectCast()->SetAllowShares(m_pMenuPresetsShares->isChecked()); else if (Action == m_pMenuPresetsNoAdmin) SandBoxes.first().objectCast()->SetDropRights(m_pMenuPresetsNoAdmin->isChecked()); - + else if (Action == m_pMenuOptions) + { + COptionsWindow* pOptionsWindow = new COptionsWindow(SandBoxes.first(), SandBoxes.first()->GetName(), this); + pOptionsWindow->show(); + } else if (Action == m_pMenuRename) { QString OldValue = SandBoxes.first()->GetName().replace("_", " "); - QString Value = QInputDialog::getText(this, "Sandboxie-Plus", "Please enter a new name for the Sandbox.", QLineEdit::Normal, OldValue); + QString Value = QInputDialog::getText(this, "Sandboxie-Plus", tr("Please enter a new name for the Sandbox."), QLineEdit::Normal, OldValue); if (Value.isEmpty() || Value == OldValue) return; Results.append((SandBoxes.first()->RenameBox(Value))); @@ -281,6 +288,17 @@ void CSbieView::OnProcessAction() CSandMan::CheckResults(Results); } +void CSbieView::OnDoubleClicked(const QModelIndex& index) +{ + QModelIndex ModelIndex = m_pSortProxy->mapToSource(index); + CSandBoxPtr pBox = m_pSbieModel->GetSandBox(ModelIndex); + if (pBox.isNull()) + return; + + COptionsWindow* pOptionsWindow = new COptionsWindow(pBox, pBox->GetName(), this); + pOptionsWindow->show(); +} + void CSbieView::ProcessSelection(const QItemSelection& selected, const QItemSelection& deselected) { if (selected.empty()) diff --git a/SandboxiePlus/SandMan/Views/SbieView.h b/SandboxiePlus/SandMan/Views/SbieView.h index 8c3e41a3c5..9e8c7a480d 100644 --- a/SandboxiePlus/SandMan/Views/SbieView.h +++ b/SandboxiePlus/SandMan/Views/SbieView.h @@ -21,6 +21,7 @@ public slots: private slots: void OnToolTipCallback(const QVariant& ID, QString& ToolTip); + void OnDoubleClicked(const QModelIndex& index); void ProcessSelection(const QItemSelection& selected, const QItemSelection& deselected); void OnSandBoxAction(); @@ -52,6 +53,7 @@ private slots: QAction* m_pMenuPresetsINet; QAction* m_pMenuPresetsShares; QAction* m_pMenuPresetsNoAdmin; + QAction* m_pMenuOptions; QAction* m_pMenuEmptyBox; QAction* m_pMenuCleanUp; QAction* m_pMenuRemove; diff --git a/SandboxiePlus/SandMan/Windows/OptionsWindow.cpp b/SandboxiePlus/SandMan/Windows/OptionsWindow.cpp new file mode 100644 index 0000000000..3f5753fe60 --- /dev/null +++ b/SandboxiePlus/SandMan/Windows/OptionsWindow.cpp @@ -0,0 +1,1502 @@ +#include "stdafx.h" +#include "OptionsWindow.h" +#include "SandMan.h" +#include "../MiscHelpers/Common/Settings.h" +#include "../MiscHelpers/Common/Common.h" +#include "../MiscHelpers/Common/ComboInputDialog.h" +#include "Helpers/WinAdmin.h" +#include + +class CustomTabStyle : public QProxyStyle { +public: + QSize sizeFromContents(ContentsType type, const QStyleOption* option, + const QSize& size, const QWidget* widget) const { + QSize s = QProxyStyle::sizeFromContents(type, option, size, widget); + if (type == QStyle::CT_TabBarTab) { + s.transpose(); + s.setHeight(s.height() * 15 / 10); + } + return s; + } + + void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const { + if (element == CE_TabBarTabLabel) { + if (const QStyleOptionTab* tab = qstyleoption_cast(option)) { + QStyleOptionTab opt(*tab); + opt.shape = QTabBar::RoundedNorth; + QProxyStyle::drawControl(element, &opt, painter, widget); + return; + } + } + QProxyStyle::drawControl(element, option, painter, widget); + } +}; + + +COptionsWindow::COptionsWindow(const QSharedPointer& pBox, const QString& Name, QWidget *parent) + : QMainWindow(parent) +{ + m_pBox = pBox; + + m_Template = pBox->GetName().left(9).compare("Template_", Qt::CaseInsensitive) == 0; + bool ReadOnly = /*pBox->GetAPI()->IsConfigLocked() ||*/ (m_Template && pBox->GetName().mid(9, 6).compare("Local_", Qt::CaseInsensitive) != 0); + + + QWidget* centralWidget = new QWidget(); + ui.setupUi(centralWidget); + this->setCentralWidget(centralWidget); + this->setWindowTitle(tr("Sandboxie Plus - '%1' Options").arg(Name)); + + ui.tabs->setTabPosition(QTabWidget::West); + ui.tabs->tabBar()->setStyle(new CustomTabStyle()); + + if (m_Template) + { + ui.tabGeneral->setEnabled(false); + ui.tabStart->setEnabled(false); + ui.tabRestrictions->setEnabled(false); + ui.tabInternet->setEnabled(false); + ui.tabAdvanced->setEnabled(false); + ui.tabTemplates->setEnabled(false); + + for (int i = 0; i < ui.tabs->count(); i++) + ui.tabs->setTabEnabled(i, ui.tabs->widget(i)->isEnabled()); + + ui.tabs->setCurrentIndex(ui.tabs->indexOf(ui.tabAccess)); + + ui.chkShowForceTmpl->setEnabled(false); + ui.chkShowStopTmpl->setEnabled(false); + ui.chkShowAccessTmpl->setEnabled(false); + } + + m_ConfigDirty = true; + + // General + ui.cmbBoxIndicator->addItem(tr("Don't alter the window title"), "-"); + ui.cmbBoxIndicator->addItem(tr("Display [#] indicator only"), "n"); + ui.cmbBoxIndicator->addItem(tr("Display box name in title"), "y"); + + ui.cmbBoxBorder->addItem(tr("Border disabled"), "off"); + ui.cmbBoxBorder->addItem(tr("Show only when title is in focus"), "ttl"); + ui.cmbBoxBorder->addItem(tr("Always show"), "on"); + + connect(ui.cmbBoxIndicator, SIGNAL(currentIndexChanged(int)), this, SLOT(OnGeneralChanged())); + connect(ui.cmbBoxBorder, SIGNAL(currentIndexChanged(int)), this, SLOT(OnGeneralChanged())); + connect(ui.btnBorderColor, SIGNAL(pressed()), this, SLOT(OnPickColor())); + connect(ui.txtCopyLimit, SIGNAL(textChanged(const QString&)), this, SLOT(OnGeneralChanged())); + connect(ui.chkNoCopyWarn, SIGNAL(clicked(bool)), this, SLOT(OnGeneralChanged())); + // + + // Groupes + connect(ui.btnAddGroup, SIGNAL(pressed()), this, SLOT(OnAddGroup())); + connect(ui.btnAddProg, SIGNAL(pressed()), this, SLOT(OnAddProg())); + connect(ui.btnDelProg, SIGNAL(pressed()), this, SLOT(OnDelProg())); + // + + // Force + connect(ui.btnForceProg, SIGNAL(pressed()), this, SLOT(OnForceProg())); + connect(ui.btnForceDir, SIGNAL(pressed()), this, SLOT(OnForceDir())); + connect(ui.btnDelForce, SIGNAL(pressed()), this, SLOT(OnDelForce())); + connect(ui.chkShowForceTmpl, SIGNAL(clicked(bool)), this, SLOT(OnShowForceTmpl())); + // + + // Stop + connect(ui.btnAddLingering, SIGNAL(pressed()), this, SLOT(OnAddLingering())); + connect(ui.btnAddLeader, SIGNAL(pressed()), this, SLOT(OnAddLeader())); + connect(ui.btnDelStopProg, SIGNAL(pressed()), this, SLOT(OnDelStopProg())); + connect(ui.chkShowStopTmpl, SIGNAL(clicked(bool)), this, SLOT(OnShowStopTmpl())); + // + + // Start + connect(ui.chkRestrictStart, SIGNAL(clicked(bool)), this, SLOT(OnRestrictStart())); + connect(ui.btnAddStartProg, SIGNAL(pressed()), this, SLOT(OnAddStartProg())); + connect(ui.btnDelStartProg, SIGNAL(pressed()), this, SLOT(OnDelStartProg())); + connect(ui.chkStartBlockMsg, SIGNAL(clicked(bool)), this, SLOT(OnStartChanged())); + // + + // Restrictions + connect(ui.chkBlockShare, SIGNAL(clicked(bool)), this, SLOT(OnRestrictionChanged())); + connect(ui.chkDropRights, SIGNAL(clicked(bool)), this, SLOT(OnRestrictionChanged())); + connect(ui.chkNoDefaultCOM, SIGNAL(clicked(bool)), this, SLOT(OnRestrictionChanged())); + connect(ui.chkProtectSCM, SIGNAL(clicked(bool)), this, SLOT(OnRestrictionChanged())); + connect(ui.chkProtectRpcSs, SIGNAL(clicked(bool)), this, SLOT(OnRestrictionChanged())); + connect(ui.chkProtectSystem, SIGNAL(clicked(bool)), this, SLOT(OnRestrictionChanged())); + // + + // INet + connect(ui.chkBlockINet, SIGNAL(clicked(bool)), this, SLOT(OnBlockINet())); + connect(ui.btnAddINetProg, SIGNAL(pressed()), this, SLOT(OnAddINetProg())); + connect(ui.btnDelINetProg, SIGNAL(pressed()), this, SLOT(OnDelINetProg())); + connect(ui.chkINetBlockMsg, SIGNAL(clicked(bool)), this, SLOT(OnINetBlockChanged())); + // + + // Access + connect(ui.btnAddFile, SIGNAL(pressed()), this, SLOT(OnAddFile())); + connect(ui.btnAddKey, SIGNAL(pressed()), this, SLOT(OnAddKey())); + connect(ui.btnAddIPC, SIGNAL(pressed()), this, SLOT(OnAddIPC())); + connect(ui.btnAddClsId, SIGNAL(pressed()), this, SLOT(OnAddClsId())); + connect(ui.btnAddCOM, SIGNAL(pressed()), this, SLOT(OnAddCOM())); + // todo: add priority by order + ui.btnMoveUp->setVisible(false); + ui.btnMoveDown->setVisible(false); + connect(ui.chkShowAccessTmpl, SIGNAL(clicked(bool)), this, SLOT(OnShowAccessTmpl())); + connect(ui.btnDelAccess, SIGNAL(pressed()), this, SLOT(OnDelAccess())); + + connect(ui.treeAccess, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(OnAccessItemClicked(QTreeWidgetItem*, int))); + connect(ui.treeAccess, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(OnAccessItemDoubleClicked(QTreeWidgetItem*, int))); + // + + // Advanced + ui.cmbEmptyCmd->addItem("%SystemRoot%\\System32\\cmd.exe /c RMDIR /s /q \"%SANDBOX%\""); + + connect(ui.chkProtectBox, SIGNAL(clicked(bool)), this, SLOT(OnAdvancedChanged())); + connect(ui.chkAutoEmpty, SIGNAL(clicked(bool)), this, SLOT(OnAdvancedChanged())); + connect(ui.cmbEmptyCmd, SIGNAL(currentTextChanged(const QString&)), this, SLOT(OnAdvancedChanged())); + connect(ui.btnAddUser, SIGNAL(pressed()), this, SLOT(OnAddUser())); + connect(ui.btnDelUser, SIGNAL(pressed()), this, SLOT(OnDelUser())); + // + + // Templates + connect(ui.cmbCategories, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFilterTemplates())); + connect(ui.treeTemplates, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(OnTemplateClicked(QTreeWidgetItem*, int))); + connect(ui.treeTemplates, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(OnTemplateDoubleClicked(QTreeWidgetItem*, int))); + // + + connect(ui.tabs, SIGNAL(currentChanged(int)), this, SLOT(OnTab())); + + // edit + connect(ui.btnEditIni, SIGNAL(pressed()), this, SLOT(OnEditIni())); + connect(ui.btnSaveIni, SIGNAL(pressed()), this, SLOT(OnSaveIni())); + connect(ui.btnCancelEdit, SIGNAL(pressed()), this, SLOT(OnCancelEdit())); + // + + connect(ui.buttonBox->button(QDialogButtonBox::Ok), SIGNAL(pressed()), this, SLOT(accept())); + connect(ui.buttonBox->button(QDialogButtonBox::Apply), SIGNAL(pressed()), this, SLOT(apply())); + connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + if (ReadOnly) { + ui.btnEditIni->setEnabled(false); + ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui.buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); + } + + OnTab(); // -> LoadConfig(); + + restoreGeometry(theConf->GetBlob("OptionsWindow/Window_Geometry")); +} + +COptionsWindow::~COptionsWindow() +{ + theConf->SetBlob("OptionsWindow/Window_Geometry",saveGeometry()); +} + +void COptionsWindow::closeEvent(QCloseEvent *e) +{ + this->deleteLater(); +} + +void COptionsWindow::LoadConfig() +{ + m_ConfigDirty = false; + + { + QString BoxNameTitle = m_pBox->GetText("BoxNameTitle", "n"); + ui.cmbBoxIndicator->setCurrentIndex(ui.cmbBoxIndicator->findData(BoxNameTitle.toLower())); + + QStringList BorderCfg = m_pBox->GetText("BorderColor").split(","); + ui.cmbBoxBorder->setCurrentIndex(ui.cmbBoxBorder->findData(BorderCfg.size() >= 2 ? BorderCfg[1].toLower() : "on")); + m_BorderColor = QColor("#" + BorderCfg[0].mid(5, 2) + BorderCfg[0].mid(3, 2) + BorderCfg[0].mid(1, 2)); + ui.btnBorderColor->setStyleSheet("background-color: " + m_BorderColor.name()); + + ui.txtCopyLimit->setText(QString::number(m_pBox->GetNum("CopyLimitKb", 80 * 1024))); + ui.chkNoCopyWarn->setChecked(!m_pBox->GetBool("CopyLimitSilent", false)); + + m_GeneralChanged = false; + } + + LoadGroups(); + + LoadForced(); + + LoadStop(); + + { + ui.chkStartBlockMsg->setChecked(m_pBox->GetBool("NotifyStartRunAccessDenied", true)); + + m_StartChanged = false; + } + + { + ui.chkBlockShare->setChecked(m_pBox->GetBool("BlockNetworkFiles", true)); + ui.chkDropRights->setChecked(m_pBox->GetBool("DropAdminRights", false)); + ui.chkNoDefaultCOM->setChecked(!m_pBox->GetBool("OpenDefaultClsid", true)); + ui.chkProtectSCM->setChecked(!m_pBox->GetBool("UnrestrictedSCM", false)); + ui.chkProtectRpcSs->setChecked(m_pBox->GetBool("ProtectRpcSs", false)); + ui.chkProtectSystem->setChecked(!m_pBox->GetBool("ExposeBoxedSystem", false)); + + m_RestrictionChanged = false; + } + + { + ui.chkINetBlockMsg->setChecked(m_pBox->GetBool("NotifyInternetAccessDenied", true)); + + m_INetBlockChanged = false; + } + + LoadAccessList(); + + { + ui.chkProtectBox->setChecked(m_pBox->GetBool("NeverDelete", false)); + ui.chkAutoEmpty->setChecked(m_pBox->GetBool("AutoDelete", false)); + ui.cmbEmptyCmd->setCurrentText(m_pBox->GetText("DeleteCommand", "")); + + QStringList Users = m_pBox->GetText("Enabled").split(","); + ui.lstUsers->clear(); + if (Users.count() > 1) + ui.lstUsers->addItems(Users.mid(1)); + + m_AdvancedChanged = false; + } + + { + LoadTemplates(); + m_TemplatesChanged = false; + } +} + +void COptionsWindow::SaveConfig() +{ + if (m_GeneralChanged) + { + m_pBox->SetText("BoxNameTitle", ui.cmbBoxIndicator->currentData().toString()); + + QStringList BorderCfg; + BorderCfg.append(QString("#%1,%2,%3").arg(m_BorderColor.blue(), 2, 16, QChar('0')).arg(m_BorderColor.green(), 2, 16, QChar('0')).arg(m_BorderColor.red(), 2, 16, QChar('0'))); + BorderCfg.append(ui.cmbBoxBorder->currentData().toString()); + //BorderCfg.append(5) // width + m_pBox->SetText("BorderColor", BorderCfg.join(",")); + + m_pBox->SetNum("CopyLimitKb", ui.txtCopyLimit->text().toInt()); + m_pBox->SetBool("CopyLimitSilent", ui.chkNoCopyWarn->isChecked()); + + m_GeneralChanged = false; + } + + if (m_GroupsChanged) + SaveGroups(); + + if (m_ForcedChanged) + SaveForced(); + + if (m_StopChanged) + SaveStop(); + + if (m_StartChanged) + { + m_pBox->SetBool("NotifyStartRunAccessDenied", ui.chkStartBlockMsg->isChecked()); + + m_StartChanged = false; + } + + if (m_RestrictionChanged) + { + m_pBox->SetBool("BlockNetworkFiles", ui.chkBlockShare->isChecked()); + m_pBox->SetBool("DropAdminRights", ui.chkDropRights->isChecked()); + m_pBox->SetBool("OpenDefaultClsid", ui.chkNoDefaultCOM->isChecked()); + m_pBox->SetBool("UnrestrictedSCM", ui.chkProtectSCM->isChecked()); + m_pBox->SetBool("ProtectRpcSs", ui.chkProtectRpcSs->isChecked()); + m_pBox->SetBool("ExposeBoxedSystem", ui.chkProtectSystem->isChecked()); + + m_RestrictionChanged = false; + } + + if (m_INetBlockChanged) + { + m_pBox->SetBool("NotifyInternetAccessDenied", ui.chkINetBlockMsg->isChecked()); + + m_INetBlockChanged = false; + } + + if (m_AccessChanged) + SaveAccessList(); + + if (m_AdvancedChanged) + { + m_pBox->SetBool("NeverDelete", ui.chkProtectBox->isChecked()); + m_pBox->SetBool("AutoDelete", ui.chkAutoEmpty->isChecked()); + m_pBox->SetText("DeleteCommand", ui.cmbEmptyCmd->currentText()); + + QStringList Users; + for (int i = 0; i < ui.lstUsers->count(); i++) + Users.append(ui.lstUsers->item(i)->text()); + m_pBox->SetText("Enabled", Users.count() > 0 ? "y," + Users.join(",") : "y"); + + m_AdvancedChanged = false; + } + + if (m_TemplatesChanged) + SaveTemplates(); +} + +void COptionsWindow::apply() +{ + if (!ui.btnEditIni->isEnabled()) + SaveIniSection(); + else + SaveConfig(); + + LoadConfig(); + + emit OptionsChanged(); +} + +void COptionsWindow::accept() +{ + apply(); + + this->close(); +} + +void COptionsWindow::reject() +{ + this->close(); +} + +void COptionsWindow::OnGeneralChanged() +{ + m_GeneralChanged = true; + + ui.lblCopyLimit->setText(tr("kilobytes (%1)").arg(FormatSize(ui.txtCopyLimit->text().toInt() * 1024))); +} + +void COptionsWindow::OnPickColor() +{ + QColor color = QColorDialog::getColor(m_BorderColor, this, "Select color"); + if (!color.isValid()) + return; + m_GeneralChanged = true; + m_BorderColor = color; + ui.btnBorderColor->setStyleSheet("background-color: " + m_BorderColor.name()); +} + +void COptionsWindow::SetProgramItem(QString Program, QTreeWidgetItem* pItem, int Column) +{ + pItem->setData(Column, Qt::UserRole, Program); + if (Program.left(1) == "<") + Program = tr("Group: %1").arg(Program.mid(1, Program.length() - 2)); + pItem->setText(Column, Program); +} + +void COptionsWindow::LoadGroups() +{ + m_TemplateGroups.clear(); + ui.treeGroups->clear(); + + QMultiMap GroupMap; // if we have a duplicate we want to know it + QSet LocalGroups; + + QStringList ProcessGroups = m_pBox->GetTextList("ProcessGroup", m_Template); + foreach(const QString& Group, ProcessGroups) + { + QStringList Entries = Group.split(","); + QString GroupName = Entries.takeFirst(); + GroupMap.insertMulti(GroupName, Entries); + LocalGroups.insert(GroupName); + } + + foreach(const QString& Template, m_pBox->GetTemplates()) + { + foreach(const QString& Group, m_pBox->GetTextListTmpl("ProcessGroup", Template)) + { + m_TemplateGroups.insert(Group); + QStringList Entries = Group.split(","); + QString GroupName = Entries.takeFirst(); + if (LocalGroups.contains(GroupName)) + continue; // local group definitions overwrite template once + GroupMap.insertMulti(GroupName, Entries); + } + } + + for(QMultiMap::iterator I = GroupMap.begin(); I != GroupMap.end(); ++I) + { + QString GroupName = I.key(); + QStringList Entries = I.value(); + QTreeWidgetItem* pItem = new QTreeWidgetItem(); + pItem->setData(0, Qt::UserRole, GroupName); + if (GroupName.length() > 2) + GroupName = GroupName.mid(1, GroupName.length() - 2); + pItem->setText(0, GroupName); + for (int i = 1; i < Entries.count(); i++) + { + QTreeWidgetItem* pSubItem = new QTreeWidgetItem(); + SetProgramItem(Entries[i], pSubItem, 0); + pItem->addChild(pSubItem); + } + ui.treeGroups->addTopLevelItem(pItem); + } + ui.treeGroups->expandAll(); + + m_GroupsChanged = false; +} + +void COptionsWindow::SaveGroups() +{ + QStringList ProcessGroups; + for (int i = 0; i < ui.treeGroups->topLevelItemCount(); i++) + { + QTreeWidgetItem* pItem = ui.treeGroups->topLevelItem(i); + QString GroupName = pItem->data(0, Qt::UserRole).toString(); + QStringList Programs; + for (int j = 0; j < pItem->childCount(); j++) + Programs.append(pItem->child(j)->data(0, Qt::UserRole).toString()); + QString Group = GroupName + "," + Programs.join(","); + if (m_TemplateGroups.contains(Group)) + continue; // don't save unchanged groups to local config + ProcessGroups.append(Group); + } + + m_pBox->UpdateTextList("ProcessGroup", ProcessGroups); + + m_GroupsChanged = false; +} + +void COptionsWindow::OnAddGroup() +{ + QString Value = QInputDialog::getText(this, "Sandboxie-Plus", tr("Please enter a name for the new group"), QLineEdit::Normal, "NewGroup"); + if (Value.isEmpty()) + return; + + for (int i = 0; i < ui.treeGroups->topLevelItemCount(); i++) { + QTreeWidgetItem* pItem = ui.treeGroups->topLevelItem(i); + if (pItem->text(0).compare(Value, Qt::CaseInsensitive) == 0) + return; + } + + QTreeWidgetItem* pItem = new QTreeWidgetItem(); + pItem->setText(0, Value); + pItem->setData(0, Qt::UserRole, "<" + Value + ">"); + ui.treeGroups->addTopLevelItem(pItem); + + m_GroupsChanged = true; +} + +QString COptionsWindow::SelectProgram(bool bOrGroup) +{ + CComboInputDialog progDialog(this); + progDialog.setText(tr("Enter program:")); + progDialog.setEditable(true); + + if (bOrGroup) + { + for (int i = 0; i < ui.treeGroups->topLevelItemCount(); i++) { + QTreeWidgetItem* pItem = ui.treeGroups->topLevelItem(i); + progDialog.addItem(tr("Group: %1").arg(pItem->text(0)), pItem->data(0, Qt::UserRole).toString()); + } + } + + progDialog.setValue(""); + + if (!progDialog.exec()) + return QString(); + + QString Program = progDialog.value(); + int Index = progDialog.findValue(Program); + if (Index != -1) + Program = progDialog.data().toString(); + + return Program; +} + +void COptionsWindow::OnAddProg() +{ + QTreeWidgetItem* pItem = ui.treeGroups->currentItem(); + while (pItem && pItem->parent()) + pItem = pItem->parent(); + + if (!pItem) + { + QMessageBox::warning(this, "SandboxiePlus", tr("Please sellect group first.")); + return; + } + + QString Value = SelectProgram(); + if (Value.isEmpty()) + return; + + QTreeWidgetItem* pSubItem = new QTreeWidgetItem(); + SetProgramItem(Value, pSubItem, 0); + pItem->addChild(pSubItem); + + m_GroupsChanged = true; +} + +void COptionsWindow::OnDelProg() +{ + QTreeWidgetItem* pItem = ui.treeGroups->currentItem(); + if (!pItem) + return; + + delete pItem; + + m_GroupsChanged = true; +} + +void COptionsWindow::CopyGroupToList(const QString& Groupe, QTreeWidget* pTree) +{ + pTree->clear(); + + for (int i = 0; i < ui.treeGroups->topLevelItemCount(); i++) + { + QTreeWidgetItem* pItem = ui.treeGroups->topLevelItem(i); + if (pItem->data(0, Qt::UserRole).toString().compare(Groupe, Qt::CaseInsensitive) == 0) + { + for (int j = 0; j < pItem->childCount(); j++) + { + QString Value = pItem->child(j)->data(0, Qt::UserRole).toString(); + + QTreeWidgetItem* pSubItem = new QTreeWidgetItem(); + SetProgramItem(Value, pSubItem, 0); + pTree->addTopLevelItem(pSubItem); + } + break; + } + } +} + +void COptionsWindow::LoadForced() +{ + ui.treeForced->clear(); + + foreach(const QString& Value, m_pBox->GetTextList("ForceProcess", m_Template)) + AddForcedEntry(Value, 1); + + foreach(const QString& Value, m_pBox->GetTextList("ForceFolder", m_Template)) + AddForcedEntry(Value, 2); + + if (ui.chkShowForceTmpl->isChecked()) + { + foreach(const QString& Template, m_pBox->GetTemplates()) + { + foreach(const QString& Value, m_pBox->GetTextListTmpl("ForceProcess", Template)) + AddForcedEntry(Value, 1, Template); + + foreach(const QString& Value, m_pBox->GetTextListTmpl("ForceFolder", Template)) + AddForcedEntry(Value, 2, Template); + } + } + + m_ForcedChanged = false; +} + +void COptionsWindow::AddForcedEntry(const QString& Name, int type, const QString& Template) +{ + QTreeWidgetItem* pItem = new QTreeWidgetItem(); + pItem->setText(0, (type == 1 ? tr("Process") : tr("Folder")) + (Template.isEmpty() ? "" : (" (" + Template + ")"))); + pItem->setData(0, Qt::UserRole, Template.isEmpty() ? type : -1); + SetProgramItem(Name, pItem, 1); + ui.treeForced->addTopLevelItem(pItem); +} + +void COptionsWindow::SaveForced() +{ + QStringList ForceProcess; + QStringList ForceFolder; + for (int i = 0; i < ui.treeForced->topLevelItemCount(); i++) + { + QTreeWidgetItem* pItem = ui.treeForced->topLevelItem(i); + int Type = pItem->data(0, Qt::UserRole).toInt(); + if (Type == -1) + continue; // entry from template + switch (Type) + { + case 1: ForceProcess.append(pItem->data(1, Qt::UserRole).toString()); break; + case 2: ForceFolder.append(pItem->data(1, Qt::UserRole).toString()); break; + } + } + + m_pBox->UpdateTextList("ForceProcess", ForceProcess); + m_pBox->UpdateTextList("ForceFolder", ForceFolder); + + m_ForcedChanged = false; +} + +void COptionsWindow::OnForceProg() +{ + QString Value = SelectProgram(); + if (Value.isEmpty()) + return; + AddForcedEntry(Value, 1); + m_ForcedChanged = true; +} + +void COptionsWindow::OnForceDir() +{ + QString Value = QFileDialog::getExistingDirectory(this, tr("Select Directory")); + if (Value.isEmpty()) + return; + AddForcedEntry(Value, 2); + m_ForcedChanged = true; +} + +void COptionsWindow::OnDelForce() +{ + DeleteAccessEntry(ui.treeForced->currentItem()); + m_ForcedChanged = true; +} + +void COptionsWindow::LoadStop() +{ + ui.treeStop->clear(); + + foreach(const QString& Value, m_pBox->GetTextList("LingerProcess", m_Template)) + AddStopEntry(Value, 1); + + foreach(const QString& Value, m_pBox->GetTextList("LeaderProcess", m_Template)) + AddStopEntry(Value, 2); + + if (ui.chkShowStopTmpl->isChecked()) + { + foreach(const QString& Template, m_pBox->GetTemplates()) + { + foreach(const QString& Value, m_pBox->GetTextListTmpl("LingerProcess", Template)) + AddStopEntry(Value, 1, Template); + + foreach(const QString& Value, m_pBox->GetTextListTmpl("LeaderProcess", Template)) + AddStopEntry(Value, 2, Template); + } + } + + m_StopChanged = false; +} + +void COptionsWindow::AddStopEntry(const QString& Name, int type, const QString& Template) +{ + QTreeWidgetItem* pItem = new QTreeWidgetItem(); + pItem->setText(0, (type == 1 ? tr("Lingerer") : tr("Leader")) + (Template.isEmpty() ? "" : (" (" + Template + ")"))); + pItem->setData(0, Qt::UserRole, Template.isEmpty() ? type : -1); + SetProgramItem(Name, pItem, 1); + ui.treeStop->addTopLevelItem(pItem); +} + +void COptionsWindow::SaveStop() +{ + QStringList LingerProcess; + QStringList LeaderProcess; + for (int i = 0; i < ui.treeForced->topLevelItemCount(); i++) + { + QTreeWidgetItem* pItem = ui.treeForced->topLevelItem(i); + int Type = pItem->data(0, Qt::UserRole).toInt(); + if (Type == -1) + continue; // entry from template + switch (Type) + { + case 1: LingerProcess.append(pItem->data(1, Qt::UserRole).toString()); break; + case 2: LeaderProcess.append(pItem->data(1, Qt::UserRole).toString()); break; + } + } + + m_pBox->UpdateTextList("LingerProcess", LingerProcess); + m_pBox->UpdateTextList("LeaderProcess", LeaderProcess); + + m_StopChanged = false; +} + +void COptionsWindow::OnAddLingering() +{ + QString Value = SelectProgram(); + if (Value.isEmpty()) + return; + AddStopEntry(Value, 1); + m_StopChanged = true; +} + +void COptionsWindow::OnAddLeader() +{ + QString Value = SelectProgram(); + if (Value.isEmpty()) + return; + AddStopEntry(Value, 2); + m_StopChanged = true; +} + +void COptionsWindow::OnDelStopProg() +{ + DeleteAccessEntry(ui.treeStop->currentItem()); + m_StopChanged = true; +} + +void COptionsWindow::OnRestrictStart() +{ + bool Enable = ui.chkRestrictStart->isChecked(); + if (Enable) + SetAccessEntry(eIPC, "!", eClosed, "*"); + else + DelAccessEntry(eIPC, "!", eClosed, "*"); + //m_StartChanged = true; +} + +void COptionsWindow::OnAddStartProg() +{ + AddProgToGroup(ui.treeStart, ""); + //m_StartChanged = true; +} + +void COptionsWindow::OnDelStartProg() +{ + DelProgFromGroup(ui.treeStart, ""); + //m_StartChanged = true; +} + +void COptionsWindow::OnBlockINet() +{ + bool Enable = ui.chkBlockINet->isChecked(); + if (Enable) + SetAccessEntry(eFile, "!", eClosed, "InternetAccessDevices"); + else + DelAccessEntry(eFile, "!", eClosed, "InternetAccessDevices"); + //m_INetBlockChanged = true; +} + +void COptionsWindow::OnAddINetProg() +{ + AddProgToGroup(ui.treeINet, ""); + //m_INetBlockChanged = true; +} + +void COptionsWindow::OnDelINetProg() +{ + DelProgFromGroup(ui.treeINet, ""); + //m_INetBlockChanged = true; +} + +void COptionsWindow::AddProgToGroup(QTreeWidget* pTree, const QString& Groupe) +{ + QString Value = SelectProgram(); + if (Value.isEmpty()) + return; + + QTreeWidgetItem* pItem = new QTreeWidgetItem(); + SetProgramItem(Value, pItem, 0); + pTree->addTopLevelItem(pItem); + + AddProgToGroup(Value, Groupe); +} + +void COptionsWindow::AddProgToGroup(const QString& Value, const QString& Groupe) +{ + QTreeWidgetItem* pGroupItem = NULL; + for (int i = 0; i < ui.treeGroups->topLevelItemCount(); i++) + { + QTreeWidgetItem* pCurItem = ui.treeGroups->topLevelItem(i); + if (pCurItem->data(0, Qt::UserRole).toString().compare(Groupe, Qt::CaseInsensitive) == 0) + { + pGroupItem = pCurItem; + break; + } + } + + if (!pGroupItem) + { + pGroupItem = new QTreeWidgetItem(); + pGroupItem->setText(0, Groupe.mid(1, Groupe.length()-2)); + pGroupItem->setData(0, Qt::UserRole, Groupe); + ui.treeGroups->addTopLevelItem(pGroupItem); + } + + QTreeWidgetItem* pProgItem = new QTreeWidgetItem(); + SetProgramItem(Value, pProgItem, 0); + pGroupItem->addChild(pProgItem); + + m_GroupsChanged = true; +} + +void COptionsWindow::DelProgFromGroup(QTreeWidget* pTree, const QString& Groupe) +{ + QTreeWidgetItem* pItem = pTree->currentItem(); + if (!pItem) + return; + + QString Value = pItem->data(0, Qt::UserRole).toString(); + + delete pItem; + + for (int i = 0; i < ui.treeGroups->topLevelItemCount(); i++) + { + QTreeWidgetItem* pGroupItem = ui.treeGroups->topLevelItem(i); + if (pGroupItem->data(0, Qt::UserRole).toString().compare(Groupe, Qt::CaseInsensitive) == 0) + { + for (int j = 0; j < pGroupItem->childCount(); j++) + { + QTreeWidgetItem* pProgItem = pGroupItem->child(j); + if (pProgItem->data(0, Qt::UserRole).toString().compare(Value, Qt::CaseInsensitive) == 0) + { + delete pProgItem; + m_GroupsChanged = true; + break; + } + } + break; + } + } +} + +QTreeWidgetItem* COptionsWindow::GetAccessEntry(EAccessType Type, const QString& Program, EAccessMode Mode, const QString& Path) +{ + for (int i = 0; i < ui.treeAccess->topLevelItemCount(); i++) + { + QTreeWidgetItem* pItem = ui.treeAccess->topLevelItem(i); + if (pItem->data(0, Qt::UserRole).toInt() == Type + && pItem->data(1, Qt::UserRole).toString().compare(Program, Qt::CaseInsensitive) == 0 + && pItem->data(2, Qt::UserRole).toInt() == Mode + && pItem->data(3, Qt::UserRole).toString().compare(Path, Qt::CaseInsensitive) == 0) + return pItem; + } + return NULL; +} + +void COptionsWindow::SetAccessEntry(EAccessType Type, const QString& Program, EAccessMode Mode, const QString& Path) +{ + if (GetAccessEntry(Type, Program, Mode, Path) != NULL) + return; // already set + AddAccessEntry(Type, Mode, Program, Path); +} + +void COptionsWindow::DelAccessEntry(EAccessType Type, const QString& Program, EAccessMode Mode, const QString& Path) +{ + if(QTreeWidgetItem* pItem = GetAccessEntry(Type, Program, Mode, Path)) + { + delete pItem; + m_AccessChanged = true; + } +} + +QString COptionsWindow::AccessTypeToName(EAccessEntry Type) +{ + switch (Type) + { + case eOpenFilePath: return "OpenFilePath"; + case eOpenPipePath: return "OpenPipePath"; + case eClosedFilePath: return "ClosedFilePath"; + case eReadFilePath: return "ReadFilePath"; + case eWriteFilePath: return "WriteFilePath"; + + case eOpenKeyPath: return "OpenKeyPath"; + case eClosedKeyPath: return "ClosedKeyPath"; + case eReadKeyPath: return "ReadKeyPath"; + case eWriteKeyPath: return "WriteKeyPath"; + + case eOpenIpcPath: return "OpenIpcPath"; + case eClosedIpcPath: return "ClosedIpcPath"; + + case eOpenWinClass: return "OpenWinClass"; + + case eOpenClsid: return "OpenClsid"; + } + return "Unknown"; +} + +void COptionsWindow::LoadAccessList() +{ + ui.treeAccess->clear(); + + for (int i = 0; i < eMaxAccessType; i++) + { + foreach(const QString& Value, m_pBox->GetTextList(AccessTypeToName((EAccessEntry)i), m_Template)) + ParseAndAddAccessEntry((EAccessEntry)i, Value); + } + + if (ui.chkShowAccessTmpl->isChecked()) + { + foreach(const QString& Template, m_pBox->GetTemplates()) + { + for (int i = 0; i < eMaxAccessType; i++) + { + foreach(const QString& Value, m_pBox->GetTextListTmpl(AccessTypeToName((EAccessEntry)i), Template)) + ParseAndAddAccessEntry((EAccessEntry)i, Value, Template); + } + } + } + + m_AccessChanged = false; +} + +void COptionsWindow::ParseAndAddAccessEntry(EAccessEntry EntryType, const QString& Value, const QString& Template) +{ + EAccessType Type; + EAccessMode Mode; + switch (EntryType) + { + case eOpenFilePath: Type = eFile; Mode = eDirect; break; + case eOpenPipePath: Type = eFile; Mode = eFull; break; + case eClosedFilePath: Type = eFile; Mode = eClosed; break; + case eReadFilePath: Type = eFile; Mode = eReadOnly; break; + case eWriteFilePath: Type = eFile; Mode = eWriteOnly; break; + + case eOpenKeyPath: Type = eKey; Mode = eDirect; break; + case eClosedKeyPath: Type = eKey; Mode = eClosed; break; + case eReadKeyPath: Type = eKey; Mode = eReadOnly; break; + case eWriteKeyPath: Type = eKey; Mode = eWriteOnly; break; + + case eOpenIpcPath: Type = eIPC; Mode = eDirect; break; + case eClosedIpcPath: Type = eIPC; Mode = eClosed; break; + + case eOpenWinClass: Type = eWndCls; Mode = eDirect; break; + + case eOpenClsid: Type = eClsId; Mode = eDirect; break; + + default: return; + } + + QStringList Values = Value.split(","); + if (Values.count() >= 2) + AddAccessEntry(Type, Mode, Values[0], Values[1], Template); + else // all programs + AddAccessEntry(Type, Mode, "", Values[0], Template); +} + +QString COptionsWindow::GetAccessModeStr(EAccessMode Mode) +{ + switch (Mode) + { + case eDirect: return "Direct"; + case eFull: return "Full"; + case eClosed: return "Closed"; + case eReadOnly: return "Read Only"; + case eWriteOnly: return "Write Only"; + } + return "Unknown"; +} + +QString COptionsWindow::GetAccessTypeStr(EAccessType Type) +{ + switch (Type) + { + case eFile: return "File/Folder"; + case eKey: return "Registry"; + case eIPC: return "IPC Path"; + case eWndCls: return "Wnd Class"; + case eClsId: return "COM Object"; + } + return "Unknown"; +} + +void COptionsWindow::AddAccessEntry(EAccessType Type, EAccessMode Mode, QString Program, const QString& Path, const QString& Template) +{ + QTreeWidgetItem* pItem = new QTreeWidgetItem(); + + pItem->setText(0, GetAccessTypeStr(Type) + (Template.isEmpty() ? "" : " (" + Template + ")")); + pItem->setData(0, Qt::UserRole, !Template.isEmpty() ? -1 : (int)Type); + + pItem->setData(1, Qt::UserRole, Program); + if (Program.isEmpty()) + Program = tr("All Programs"); + bool Not = Program.left(1) == "!"; + if (Not) + Program.remove(0, 1); + if (Program.left(1) == "<") + Program = tr("Group: %1").arg(Program.mid(1, Program.length() - 2)); + pItem->setText(1, (Not ? "NOT " : "") + Program); + + pItem->setText(2, GetAccessModeStr(Mode)); + pItem->setData(2, Qt::UserRole, (int)Mode); + + pItem->setText(3, Path); + pItem->setData(3, Qt::UserRole, Path); + + ui.treeAccess->addTopLevelItem(pItem); +} + +QString COptionsWindow::MakeAccessStr(EAccessType Type, EAccessMode Mode) +{ + switch (Type) + { + case eFile: + switch (Mode) + { + case eDirect: return "OpenFilePath"; + case eFull: return "OpenPipePath"; + case eClosed: return "ClosedFilePath"; + case eReadOnly: return "ReadFilePath"; + case eWriteOnly: return "WriteFilePath"; + } + break; + case eKey: + switch (Mode) + { + case eDirect: return "OpenKeyPath"; + case eClosed: return "ClosedKeyPath"; + case eReadOnly: return "ReadKeyPath"; + case eWriteOnly: return "WriteKeyPath"; + } + break; + case eIPC: + switch (Mode) + { + case eDirect: return "OpenIpcPath"; + case eClosed: return "ClosedIpcPath"; + } + break; + case eWndCls: + switch (Mode) + { + case eDirect: return "OpenWinClass"; + } + break; + case eClsId: + switch (Mode) + { + case eDirect: return "OpenClsid"; + } + break; + } + return "Unknown"; +} + +void COptionsWindow::SaveAccessList() +{ + QMultiMap AccessMap; + for (int i = 0; i < ui.treeAccess->topLevelItemCount(); i++) + { + QTreeWidgetItem* pItem = ui.treeAccess->topLevelItem(i); + int Type = pItem->data(0, Qt::UserRole).toInt(); + if (Type == -1) + continue; // entry from template + int Mode = pItem->data(2, Qt::UserRole).toInt(); + QString Program = pItem->data(1, Qt::UserRole).toString(); + QString Value = pItem->data(3, Qt::UserRole).toString(); + if (Program.isEmpty()) + Value.prepend(Program + ","); + AccessMap.insertMulti(MakeAccessStr((EAccessType)Type, (EAccessMode)Mode), Value); + } + + foreach(const QString& Key, AccessMap.uniqueKeys()) + m_pBox->UpdateTextList(Key, AccessMap.values(Key)); + + m_AccessChanged = false; +} + +void COptionsWindow::OnAccessItemClicked(QTreeWidgetItem* pItem, int Column) +{ + if (Column != 0) + return; + + QWidget* pProgram = ui.treeAccess->itemWidget(pItem, 1); + if (!pProgram) + return; + + QHBoxLayout* pLayout = (QHBoxLayout*)pProgram->layout(); + QToolButton* pNot = (QToolButton*)pLayout->itemAt(0)->widget(); + QComboBox* pCombo = (QComboBox*)pLayout->itemAt(1)->widget(); + + QComboBox* pMode = (QComboBox*)ui.treeAccess->itemWidget(pItem, 2); + QLineEdit* pPath = (QLineEdit*)ui.treeAccess->itemWidget(pItem, 3); + + QString Program = pCombo->currentText(); + int Index = pCombo->findText(Program); + if(Index != -1) + Program = pCombo->itemData(Index, Qt::UserRole).toString(); + + pItem->setText(1, (pNot->isChecked() ? "NOT " : "") + pCombo->currentText()); + pItem->setData(1, Qt::UserRole, (pNot->isChecked() ? "!" : "") + Program); + pItem->setText(2, GetAccessModeStr((EAccessMode)pMode->currentData().toInt())); + pItem->setData(2, Qt::UserRole, pMode->currentData()); + pItem->setText(3, pPath->text()); + pItem->setData(3, Qt::UserRole, pPath->text()); + + ui.treeAccess->setItemWidget(pItem, 1, NULL); + ui.treeAccess->setItemWidget(pItem, 2, NULL); + ui.treeAccess->setItemWidget(pItem, 3, NULL); + + m_AccessChanged = true; +} + +QList COptionsWindow::GetAccessModes(EAccessType Type) +{ + switch (Type) + { + case eFile: return QList() << eDirect << eFull << eClosed << eReadOnly << eWriteOnly; + case eKey: return QList() << eDirect << eClosed << eReadOnly << eWriteOnly; + case eIPC: return QList() << eDirect << eClosed; + } + return QList() << eDirect; +} + +void COptionsWindow::OnAccessItemDoubleClicked(QTreeWidgetItem* pItem, int Column) +{ + if (Column == 0) + return; + + int Type = pItem->data(0, Qt::UserRole).toInt(); + if (Type == -1) { + QMessageBox::warning(this, "SandboxiePlus", tr("Template values can not be edited.")); + return; + } + + QString Program = pItem->data(1, Qt::UserRole).toString(); + + QWidget* pProgram = new QWidget(); + pProgram->setAutoFillBackground(true); + QHBoxLayout* pLayout = new QHBoxLayout(); + pLayout->setMargin(0); + pLayout->setSpacing(0); + pProgram->setLayout(pLayout); + QToolButton* pNot = new QToolButton(pProgram); + pNot->setText("!"); + pNot->setCheckable(true); + if (Program.left(1) == "!"){ + pNot->setChecked(true); + Program.remove(0, 1); + } + pLayout->addWidget(pNot); + QComboBox* pCombo = new QComboBox(pProgram); + pCombo->addItem(tr("All Programs"), ""); + + // todo: add recently ran programs or programs from other configs + + for (int i = 0; i < ui.treeGroups->topLevelItemCount(); i++) { + QTreeWidgetItem* pItem = ui.treeGroups->topLevelItem(i); + pCombo->addItem(tr("Group: %1").arg(pItem->text(0)), "<" + pItem->text(0) + ">"); + } + + pCombo->setEditable(true); + int Index = pCombo->findData(Program); + pCombo->setCurrentIndex(Index); + if(Index == -1) + pCombo->setCurrentText(Program); + pLayout->addWidget(pCombo); + + ui.treeAccess->setItemWidget(pItem, 1, pProgram); + + QComboBox* pMode = new QComboBox(); + foreach(EAccessMode Mode, GetAccessModes((EAccessType)Type)) + pMode->addItem(GetAccessModeStr(Mode), (int)Mode); + pMode->setCurrentIndex(pMode->findData(pItem->data(2, Qt::UserRole))); + ui.treeAccess->setItemWidget(pItem, 2, pMode); + + QLineEdit* pPath = new QLineEdit(); + pPath->setText(pItem->data(3, Qt::UserRole).toString()); + ui.treeAccess->setItemWidget(pItem, 3, pPath); +} + +void COptionsWindow::DeleteAccessEntry(QTreeWidgetItem* pItem) +{ + if (!pItem) + return; + + if (pItem->data(0, Qt::UserRole).toInt() == -1) { + QMessageBox::warning(this, "SandboxiePlus", tr("Template values can not be removed.")); + return; + } + + delete pItem; +} + +void COptionsWindow::OnDelAccess() +{ + DeleteAccessEntry(ui.treeAccess->currentItem()); + m_AccessChanged = true; +} + +void COptionsWindow::OnAdvancedChanged() +{ + m_AdvancedChanged = true; + + ui.chkAutoEmpty->setEnabled(!ui.chkProtectBox->isChecked()); + ui.cmbEmptyCmd->setEnabled(!ui.chkProtectBox->isChecked()); +} + +#include +#include + +void COptionsWindow::OnAddUser() +{ + QStringList Users; + + IDsObjectPicker *pObjectPicker = NULL; + HRESULT hr = CoCreateInstance(CLSID_DsObjectPicker, NULL, CLSCTX_INPROC_SERVER, IID_IDsObjectPicker, (void **)&pObjectPicker); + if (FAILED(hr)) + return; + + DSOP_SCOPE_INIT_INFO ScopeInit; + memset(&ScopeInit, 0, sizeof(DSOP_SCOPE_INIT_INFO)); + ScopeInit.cbSize = sizeof(DSOP_SCOPE_INIT_INFO); + ScopeInit.flType = DSOP_SCOPE_TYPE_TARGET_COMPUTER | DSOP_SCOPE_TYPE_UPLEVEL_JOINED_DOMAIN | DSOP_SCOPE_TYPE_DOWNLEVEL_JOINED_DOMAIN; + ScopeInit.flScope = DSOP_SCOPE_FLAG_STARTING_SCOPE | DSOP_SCOPE_FLAG_DEFAULT_FILTER_USERS | DSOP_SCOPE_FLAG_DEFAULT_FILTER_GROUPS; + ScopeInit.FilterFlags.Uplevel.flBothModes = DSOP_FILTER_USERS | DSOP_FILTER_WELL_KNOWN_PRINCIPALS | DSOP_FILTER_BUILTIN_GROUPS + | DSOP_FILTER_UNIVERSAL_GROUPS_SE | DSOP_FILTER_GLOBAL_GROUPS_SE | DSOP_FILTER_DOMAIN_LOCAL_GROUPS_SE; + ScopeInit.FilterFlags.flDownlevel = DSOP_DOWNLEVEL_FILTER_USERS | DSOP_DOWNLEVEL_FILTER_LOCAL_GROUPS | DSOP_DOWNLEVEL_FILTER_GLOBAL_GROUPS; + + DSOP_INIT_INFO InitInfo; + memset(&InitInfo, 0, sizeof(InitInfo)); + InitInfo.cbSize = sizeof(InitInfo); + InitInfo.pwzTargetComputer = NULL; + InitInfo.cDsScopeInfos = 1; + InitInfo.aDsScopeInfos = &ScopeInit; + InitInfo.flOptions = DSOP_FLAG_MULTISELECT; + + hr = pObjectPicker->Initialize(&InitInfo); + + if (SUCCEEDED(hr)) + { + IDataObject *pDataObject = NULL; + hr = pObjectPicker->InvokeDialog((HWND)this->winId(), &pDataObject); + if (SUCCEEDED(hr) && pDataObject) + { + FORMATETC formatEtc; + formatEtc.cfFormat = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_DSOP_DS_SELECTION_LIST); + formatEtc.ptd = NULL; + formatEtc.dwAspect = DVASPECT_CONTENT; + formatEtc.lindex = -1; + formatEtc.tymed = TYMED_HGLOBAL; + + STGMEDIUM stgMedium; + hr = pDataObject->GetData(&formatEtc, &stgMedium); + if (SUCCEEDED(hr)) + { + PDS_SELECTION_LIST pResults = (PDS_SELECTION_LIST)GlobalLock(stgMedium.hGlobal); + if (pResults) + { + for (ULONG i = 0; i < pResults->cItems; i++) + Users.append(QString::fromWCharArray(pResults->aDsSelection[i].pwzName)); + GlobalUnlock(stgMedium.hGlobal); + } + } + pDataObject->Release(); + } + } + pObjectPicker->Release(); + + + if (Users.isEmpty()) + return; + + ui.lstUsers->addItems(Users); +} + +void COptionsWindow::OnDelUser() +{ + foreach(QListWidgetItem* pItem, ui.lstUsers->selectedItems()) + delete pItem; +} + +void COptionsWindow::LoadTemplates() +{ + m_AllTemplates.clear(); + ui.cmbCategories->clear(); + + QStringList Templates; + for (int index = 0; ; index++) + { + QString Value = m_pBox->GetAPI()->SbieIniGet("", "", index); + if (Value.isNull()) + break; + Templates.append(Value); + } + + for (QStringList::iterator I = Templates.begin(); I != Templates.end();) + { + if (I->left(9).compare("Template_", Qt::CaseInsensitive) != 0 || *I == "Template_KnownConflicts") { + I = Templates.erase(I); + continue; + } + + QString Name = *I++; + QString Category = m_pBox->GetAPI()->SbieIniGet(Name, "Tmpl.Class", 0x40000000L); // CONF_GET_NO_GLOBAL); + QString Title = m_pBox->GetAPI()->SbieIniGet(Name, "Tmpl.Title", 0x40000000L); // CONF_GET_NO_GLOBAL); + + if (Title.left(1) == "#") + { + int End = Title.mid(1).indexOf(","); + if (End == -1) End = Title.length() - 1; + int MsgNum = Title.mid(1, End).toInt(); + Title = m_pBox->GetAPI()->GetSbieMessage(MsgNum, Title.mid(End+2)); + } + if (Title.isEmpty()) Title = Name; + //else Title += " (" + Name + ")"; + if (Title == "-") + continue; // skip separators + + m_AllTemplates.insertMulti(Category, qMakePair(Name, Title)); + } + + ui.cmbCategories->addItem(tr("All Categories"), ""); + ui.cmbCategories->setCurrentIndex(0); + foreach(const QString& Category, m_AllTemplates.uniqueKeys()) + { + if (Category.isEmpty()) + continue; + ui.cmbCategories->addItem(Category, Category); + } + + m_GlobalTemplates = m_pBox->GetAPI()->GetGlobalSettings()->GetTextList("Template", false); + m_BoxTemplates = m_pBox->GetTextList("Template", false); + + ShowTemplates(); +} + +void COptionsWindow::ShowTemplates() +{ + ui.treeTemplates->clear(); + + QString Category = ui.cmbCategories->currentData().toString(); + + for (QMultiMap>::iterator I = m_AllTemplates.begin(); I != m_AllTemplates.end(); ++I) + { + if (!Category.isEmpty() && I.key().compare(Category, Qt::CaseInsensitive) != 0) + continue; + + QString Name = I.value().first.mid(9); + + QTreeWidgetItem* pItem = new QTreeWidgetItem(); + pItem->setText(0, I.key()); + pItem->setData(1, Qt::UserRole, I.value().first); + pItem->setText(1, I.value().second); + //pItem->setFlags(pItem->flags() | Qt::ItemIsUserCheckable); + if(m_GlobalTemplates.contains(Name)) + pItem->setCheckState(1, Qt::PartiallyChecked); + else if (m_BoxTemplates.contains(Name)) + pItem->setCheckState(1, Qt::Checked); + else + pItem->setCheckState(1, Qt::Unchecked); + ui.treeTemplates->addTopLevelItem(pItem); + } +} + +void COptionsWindow::OnTemplateClicked(QTreeWidgetItem* pItem, int Column) +{ + QString Name = pItem->data(1, Qt::UserRole).toString().mid(9); + if (m_GlobalTemplates.contains(Name)) { + QMessageBox::warning(this, "SandboxiePlus", tr("This template is enabled globally to configure it use the global options.")); + pItem->setCheckState(1, Qt::PartiallyChecked); + return; + } + + if (pItem->checkState(1) == Qt::Checked) { + if (!m_BoxTemplates.contains(Name)) { + m_BoxTemplates.append(Name); + m_TemplatesChanged = true; + } + } + else if (pItem->checkState(1) == Qt::Unchecked) { + if (m_BoxTemplates.contains(Name)) { + m_BoxTemplates.removeAll(Name); + m_TemplatesChanged = true; + } + } +} + +void COptionsWindow::OnTemplateDoubleClicked(QTreeWidgetItem* pItem, int Column) +{ + QSharedPointer pTemplate = QSharedPointer(new CSbieIni(pItem->data(1, Qt::UserRole).toString(), m_pBox->GetAPI())); + + COptionsWindow* pOptionsWindow = new COptionsWindow(pTemplate, pItem->text(1), this); + pOptionsWindow->show(); +} + +void COptionsWindow::SaveTemplates() +{ + m_pBox->UpdateTextList("Template", m_BoxTemplates); + + m_TemplatesChanged = false; +} + +void COptionsWindow::OnTab() +{ + if (ui.tabs->currentWidget() == ui.tabEdit) + { + LoadIniSection(); + ui.txtIniSection->setReadOnly(true); + } + else + { + if (m_ConfigDirty) + LoadConfig(); + + if (ui.tabs->currentWidget() == ui.tabStart) + { + ui.chkRestrictStart->setChecked(GetAccessEntry(eIPC, "!", eClosed, "*") != NULL); + CopyGroupToList("", ui.treeStart); + } + else if (ui.tabs->currentWidget() == ui.tabInternet) + { + ui.chkBlockINet->setChecked(GetAccessEntry(eFile, "!", eClosed, "InternetAccessDevices") != NULL); + CopyGroupToList("", ui.treeINet); + } + } +} + +void COptionsWindow::SetIniEdit(bool bEnable) +{ + for (int i = 0; i < ui.tabs->count() - 1; i++) { + bool Enabled = ui.tabs->widget(i)->isEnabled(); + ui.tabs->setTabEnabled(i, !bEnable && Enabled); + ui.tabs->widget(i)->setEnabled(Enabled); + } + ui.btnSaveIni->setEnabled(bEnable); + ui.btnCancelEdit->setEnabled(bEnable); + ui.txtIniSection->setReadOnly(!bEnable); + ui.btnEditIni->setEnabled(!bEnable); +} + +void COptionsWindow::OnEditIni() +{ + SetIniEdit(true); +} + +void COptionsWindow::OnSaveIni() +{ + SaveIniSection(); + SetIniEdit(false); +} + +void COptionsWindow::OnCancelEdit() +{ + SetIniEdit(false); +} + +void COptionsWindow::LoadIniSection() +{ + QString Section; + + m_Settings = m_pBox->GetIniSection(NULL, m_Template); + + for (QList>::const_iterator I = m_Settings.begin(); I != m_Settings.end(); ++I) + Section += I->first + "=" + I->second + "\n"; + + ui.txtIniSection->setPlainText(Section); +} + +void COptionsWindow::SaveIniSection() +{ + m_ConfigDirty = true; + + // Note: an incremental update would be more elegat but it would change the entry order in the ini, + // hence its better for the user to fully rebuild the section each time. + // + for (QList>::const_iterator I = m_Settings.begin(); I != m_Settings.end(); ++I) + m_pBox->DelValue(I->first, I->second); + + //QList> NewSettings; + //QList> OldSettings = m_Settings; + + QStringList Section = SplitStr(ui.txtIniSection->toPlainText(), "\n"); + foreach(const QString& Line, Section) + { + if (Line.isEmpty()) + return; + StrPair Settings = Split2(Line, "="); + + //if (!OldSettings.removeOne(Settings)) + // NewSettings.append(Settings); + + m_pBox->InsertText(Settings.first, Settings.second); + } + + //for (QList>::const_iterator I = OldSettings.begin(); I != OldSettings.end(); ++I) + // m_pBox->DelValue(I->first, I->second); + // + //for (QList>::const_iterator I = NewSettings.begin(); I != NewSettings.end(); ++I) + // m_pBox->InsertText(I->first, I->second); + + LoadIniSection(); +} \ No newline at end of file diff --git a/SandboxiePlus/SandMan/Windows/OptionsWindow.h b/SandboxiePlus/SandMan/Windows/OptionsWindow.h new file mode 100644 index 0000000000..32bd81996d --- /dev/null +++ b/SandboxiePlus/SandMan/Windows/OptionsWindow.h @@ -0,0 +1,196 @@ +#pragma once + +#include +#include "ui_OptionsWindow.h" +#include "SbiePlusAPI.h" + +class COptionsWindow : public QMainWindow +{ + Q_OBJECT + +public: + COptionsWindow(const QSharedPointer& pBox, const QString& Name, QWidget *parent = Q_NULLPTR); + ~COptionsWindow(); + +signals: + void OptionsChanged(); + +public slots: + void apply(); + void accept(); + void reject(); + +private slots: + + void OnPickColor(); + + void OnAddGroup(); + void OnAddProg(); + void OnDelProg(); + + void OnForceProg(); + void OnForceDir(); + void OnDelForce(); + void OnShowForceTmpl() { LoadForced(); } + + void OnAddLingering(); + void OnAddLeader(); + void OnDelStopProg(); + void OnShowStopTmpl() { LoadStop(); } + + void OnRestrictStart(); + void OnAddStartProg(); + void OnDelStartProg(); + + void OnBlockINet(); + void OnAddINetProg(); + void OnDelINetProg(); + + void OnAccessItemClicked(QTreeWidgetItem* pItem, int Column); + void OnAccessItemDoubleClicked(QTreeWidgetItem* pItem, int Column); + + void OnAddFile() { AddAccessEntry(eFile, eDirect, "", ""); } + void OnAddKey() { AddAccessEntry(eKey, eDirect, "", ""); } + void OnAddIPC() { AddAccessEntry(eIPC, eDirect, "", ""); } + void OnAddClsId() { AddAccessEntry(eWndCls, eDirect, "", ""); } + void OnAddCOM() { AddAccessEntry(eClsId, eDirect, "", ""); } + void OnDelAccess(); + void OnShowAccessTmpl() { LoadAccessList(); } + + void OnAddUser(); + void OnDelUser(); + + void OnFilterTemplates() { ShowTemplates(); } + void OnTemplateClicked(QTreeWidgetItem* pItem, int Column); + void OnTemplateDoubleClicked(QTreeWidgetItem* pItem, int Column); + + void OnTab(); + + void OnGeneralChanged(); + void OnStartChanged() { m_StartChanged = true; } + void OnRestrictionChanged() { m_RestrictionChanged = true; } + void OnINetBlockChanged() { m_INetBlockChanged = true; } + void OnAdvancedChanged(); + + void SetIniEdit(bool bEnable); + void OnEditIni(); + void OnSaveIni(); + void OnCancelEdit(); + +protected: + void closeEvent(QCloseEvent *e); + + enum EAccessEntry + { + eOpenFilePath, + eOpenPipePath, + eClosedFilePath, + eReadFilePath, + eWriteFilePath, + + eOpenKeyPath, + eClosedKeyPath, + eReadKeyPath, + eWriteKeyPath, + + eOpenIpcPath, + eClosedIpcPath, + + eOpenWinClass, + + eOpenClsid, + + eMaxAccessType + }; + + enum EAccessType + { + eFile, + eKey, + eIPC, + eWndCls, + eClsId + }; + + enum EAccessMode + { + eDirect, + eFull, + eClosed, + eReadOnly, + eWriteOnly + }; + + void SetProgramItem(QString Program, QTreeWidgetItem* pItem, int Column); + + QString SelectProgram(bool bOrGroup = true); + + void CopyGroupToList(const QString& Groupe, QTreeWidget* pTree); + QTreeWidgetItem* GetAccessEntry(EAccessType Type, const QString& Program, EAccessMode Mode, const QString& Path); + void SetAccessEntry(EAccessType Type, const QString& Program, EAccessMode Mode, const QString& Path); + void DelAccessEntry(EAccessType Type, const QString& Program, EAccessMode Mode, const QString& Path); + void AddProgToGroup(QTreeWidget* pTree, const QString& Groupe); + void AddProgToGroup(const QString& Value, const QString& Groupe); + void DelProgFromGroup(QTreeWidget* pTree, const QString& Groupe); + + void LoadConfig(); + void SaveConfig(); + + void LoadGroups(); + void SaveGroups(); + + void LoadForced(); + void AddForcedEntry(const QString& Name, int type, const QString& Template = QString()); + void SaveForced(); + + void LoadStop(); + void AddStopEntry(const QString& Name, int type, const QString& Template = QString()); + void SaveStop(); + + QString AccessTypeToName(EAccessEntry Type); + void LoadAccessList(); + QString GetAccessTypeStr(EAccessType Type); + QString GetAccessModeStr(EAccessMode Mode); + void ParseAndAddAccessEntry(EAccessEntry EntryType, const QString& Value, const QString& Template = QString()); + void AddAccessEntry(EAccessType Type, EAccessMode Mode, QString Program, const QString& Path, const QString& Template = QString()); + QString MakeAccessStr(EAccessType Type, EAccessMode Mode); + void SaveAccessList(); + QList GetAccessModes(EAccessType Type); + void DeleteAccessEntry(QTreeWidgetItem* pItem); + + void LoadTemplates(); + void ShowTemplates(); + void SaveTemplates(); + + void LoadIniSection(); + void SaveIniSection(); + + bool m_ConfigDirty; + QColor m_BorderColor; + + bool m_GeneralChanged; + bool m_GroupsChanged; + bool m_ForcedChanged; + bool m_StopChanged; + bool m_StartChanged; + bool m_RestrictionChanged; + bool m_INetBlockChanged; + bool m_AccessChanged; + bool m_TemplatesChanged; + bool m_AdvancedChanged; + + bool m_Template; + + QSet m_TemplateGroups; + + QMultiMap> m_AllTemplates; + QStringList m_GlobalTemplates; + QStringList m_BoxTemplates; + + QList> m_Settings; + + QSharedPointer m_pBox; + +private: + Ui::OptionsWindow ui; +}; diff --git a/SandboxiePlus/SandMan/Windows/SettingsWindow.cpp b/SandboxiePlus/SandMan/Windows/SettingsWindow.cpp new file mode 100644 index 0000000000..0ab181268d --- /dev/null +++ b/SandboxiePlus/SandMan/Windows/SettingsWindow.cpp @@ -0,0 +1,168 @@ +#include "stdafx.h" +#include "SettingsWindow.h" +#include "SandMan.h" +#include "../MiscHelpers/Common/Settings.h" +#include "Helpers/WinAdmin.h" + + +CSettingsWindow::CSettingsWindow(QWidget *parent) + : QMainWindow(parent) +{ + QWidget* centralWidget = new QWidget(); + ui.setupUi(centralWidget); + this->setCentralWidget(centralWidget); + this->setWindowTitle(tr("Sandboxie Plus - Settings")); + + ui.uiLang->addItem("International Englisch", ""); + QDir langDir(QApplication::applicationDirPath() + "/translations/"); + foreach(const QString& langFile, langDir.entryList(QStringList("taskexplorer_*.qm"), QDir::Files)) + { + QString Code = langFile.mid(13, langFile.length() - 13 - 3); + QLocale Locale(Code); + QString Lang = Locale.nativeLanguageName(); + ui.uiLang->addItem(Lang, Code); + } + ui.uiLang->setCurrentIndex(ui.uiLang->findData(theConf->GetString("Options/Language"))); + + ui.chkAutoStart->setChecked(IsAutorunEnabled()); + + ui.chkDarkTheme->setChecked(theConf->GetBool("Options/DarkTheme", false)); + + ui.chkNotifications->setChecked(theConf->GetBool("Options/ShowNotifications", true)); + + ui.chkWatchConfig->setChecked(theConf->GetBool("Options/WatchIni", true)); + + ui.onClose->addItem(tr("Close to Tray"), "ToTray"); + ui.onClose->addItem(tr("Prompt before Close"), "Prompt"); + ui.onClose->addItem(tr("Close"), "Close"); + ui.onClose->setCurrentIndex(ui.onClose->findData(theConf->GetString("Options/OnClose", "ToTray"))); + + ui.chkShowTray->setChecked(theConf->GetBool("Options/ShowSysTray", true)); + + connect(ui.chkShowTray, SIGNAL(stateChanged(int)), this, SLOT(OnChange())); + //connect(ui.chkUseCycles, SIGNAL(stateChanged(int)), this, SLOT(OnChange())); + + + ui.fileRoot->setText(theAPI->GetGlobalSettings()->GetText("FileRootPath")); + ui.chkSeparateUserFolders->setChecked(theAPI->GetGlobalSettings()->GetBool("SeparateUserFolders", true)); + ui.regRoot->setText(theAPI->GetGlobalSettings()->GetText("KeyRootPath")); + ui.ipcRoot->setText(theAPI->GetGlobalSettings()->GetText("IpcRootPath")); + + ui.chkAdminOnly->setChecked(theAPI->GetGlobalSettings()->GetBool("EditAdminOnly", false)); + ui.chkPassRequired->setChecked(!theAPI->GetGlobalSettings()->GetText("EditPassword", "").isEmpty()); + connect(ui.chkPassRequired, SIGNAL(stateChanged(int)), this, SLOT(OnChange())); + connect(ui.btnSetPassword, SIGNAL(pressed()), this, SLOT(OnSetPassword())); + ui.chkAdminOnlyFP->setChecked(theAPI->GetGlobalSettings()->GetBool("ForceDisableAdminOnly", false)); + ui.chkClearPass->setChecked(theAPI->GetGlobalSettings()->GetBool("ForgetPassword", false)); + + connect(ui.buttonBox->button(QDialogButtonBox::Ok), SIGNAL(pressed()), this, SLOT(accept())); + connect(ui.buttonBox->button(QDialogButtonBox::Apply), SIGNAL(pressed()), this, SLOT(apply())); + connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + restoreGeometry(theConf->GetBlob("SettingsWindow/Window_Geometry")); + + OnChange(); +} + +CSettingsWindow::~CSettingsWindow() +{ + theConf->SetBlob("SettingsWindow/Window_Geometry",saveGeometry()); +} + +void CSettingsWindow::closeEvent(QCloseEvent *e) +{ + this->deleteLater(); +} + +void CSettingsWindow::apply() +{ + theConf->SetValue("Options/Language", ui.uiLang->currentData()); + + theConf->SetValue("Options/DarkTheme", ui.chkDarkTheme->isChecked()); + + AutorunEnable(ui.chkAutoStart->isChecked()); + + theConf->SetValue("Options/ShowNotifications", ui.chkNotifications->isChecked()); + + theConf->SetValue("Options/WatchIni", ui.chkWatchConfig->isChecked()); + + theConf->SetValue("Options/OnClose", ui.onClose->currentData()); + + theConf->SetValue("Options/ShowSysTray", ui.chkShowTray->isChecked()); + + if (ui.fileRoot->text().isEmpty()) + ui.fileRoot->setText("\\??\\%SystemDrive%\\Sandbox\\%USER%\\%SANDBOX%"); + theAPI->GetGlobalSettings()->SetText("FileRootPath", ui.fileRoot->text()); + theAPI->GetGlobalSettings()->SetBool("SeparateUserFolders", ui.chkSeparateUserFolders->isChecked()); + + if (ui.regRoot->text().isEmpty()) + ui.regRoot->setText("\\REGISTRY\\USER\\Sandbox_%USER%_%SANDBOX%"); + theAPI->GetGlobalSettings()->SetText("KeyRootPath", ui.regRoot->text()); + + if (ui.ipcRoot->text().isEmpty()) + ui.ipcRoot->setText("\\Sandbox\\%USER%\\%SANDBOX%\\Session_%SESSION%"); + theAPI->GetGlobalSettings()->SetText("IpcRootPath", ui.ipcRoot->text()); + + + theAPI->GetGlobalSettings()->SetBool("EditAdminOnly", ui.chkAdminOnly->isChecked()); + + bool isPassSet = !theAPI->GetGlobalSettings()->GetText("EditPassword", "").isEmpty(); + if (ui.chkPassRequired->isChecked()) + { + if (!isPassSet && m_NewPassword.isEmpty()) + OnSetPassword(); // request password entry if it wasn't already + if (!m_NewPassword.isEmpty()) { + theAPI->LockConfig(m_NewPassword); // set new/changed password + m_NewPassword.clear(); + } + } + else if (isPassSet) + theAPI->LockConfig(QString()); // clear password + + theAPI->GetGlobalSettings()->SetBool("ForceDisableAdminOnly", ui.chkAdminOnlyFP->isChecked()); + theAPI->GetGlobalSettings()->SetBool("ForgetPassword", ui.chkClearPass->isChecked()); + + emit OptionsChanged(); +} + +void CSettingsWindow::accept() +{ + apply(); + + this->close(); +} + +void CSettingsWindow::reject() +{ + this->close(); +} + +void CSettingsWindow::OnChange() +{ + //ui.chkLinuxStyle->setEnabled(!ui.chkUseCycles->isChecked()); + + QStandardItemModel *model = qobject_cast(ui.onClose->model()); + QStandardItem *item = model->item(0); + item->setFlags((!ui.chkShowTray->isChecked()) ? item->flags() & ~Qt::ItemIsEnabled : item->flags() | Qt::ItemIsEnabled); + + ui.btnSetPassword->setEnabled(ui.chkPassRequired->isChecked()); +} + +void CSettingsWindow::OnSetPassword() +{ +retry: + QString Value1 = QInputDialog::getText(this, "Sandboxie-Plus", tr("Please enter the new configuration password."), QLineEdit::Password); + if (Value1.isEmpty()) + return; + + QString Value2 = QInputDialog::getText(this, "Sandboxie-Plus", tr("Please re enter the new configuration password."), QLineEdit::Password); + if (Value2.isEmpty()) + return; + + if (Value1 != Value2) { + QMessageBox::warning(this, "Sandboxie-Plus", tr("Passwords did not match, please retry.")); + goto retry; + } + + m_NewPassword = Value1; +} \ No newline at end of file diff --git a/SandboxiePlus/SandMan/Windows/SettingsWindow.h b/SandboxiePlus/SandMan/Windows/SettingsWindow.h new file mode 100644 index 0000000000..c9c8ddedc5 --- /dev/null +++ b/SandboxiePlus/SandMan/Windows/SettingsWindow.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include "ui_SettingsWindow.h" + +class CSettingsWindow : public QMainWindow +{ + Q_OBJECT + +public: + CSettingsWindow(QWidget *parent = Q_NULLPTR); + ~CSettingsWindow(); + +signals: + void OptionsChanged(); + +public slots: + void apply(); + void accept(); + void reject(); + +private slots: + void OnChange(); + void OnSetPassword(); + +protected: + void closeEvent(QCloseEvent *e); + + QString m_NewPassword; + +private: + Ui::SettingsWindow ui; +}; diff --git a/SandboxiePlus/SandMan/main.cpp b/SandboxiePlus/SandMan/main.cpp index 1127d2eeee..2a5cbf92fd 100644 --- a/SandboxiePlus/SandMan/main.cpp +++ b/SandboxiePlus/SandMan/main.cpp @@ -49,8 +49,6 @@ int main(int argc, char *argv[]) CSandMan* pWnd = new CSandMan(); QObject::connect(&app, SIGNAL(messageReceived(const QString&)), pWnd, SLOT(OnMessage(const QString&))); - pWnd->show(); - int ret = app.exec(); delete pWnd;