From b69809d6331b17865cc657cfd2ad48ed7464a6f2 Mon Sep 17 00:00:00 2001 From: George Hazan Date: Thu, 11 Jan 2018 20:31:51 +0300 Subject: mdbx-plugin: fix names for clarity (minor, but renaming). # Conflicts: # plugins/Dbx_mdbx/src/libmdbx --- plugins/Dbx_mdbx/dbx_mdbx.vcxproj | 51 ++++ plugins/Dbx_mdbx/dbx_mdbx.vcxproj.filters | 4 + plugins/Dbx_mdbx/res/dbx_mdbx.rc | 177 ++++++++++++ plugins/Dbx_mdbx/res/logo.ico | Bin 0 -> 5430 bytes plugins/Dbx_mdbx/res/pass.ico | Bin 0 -> 1150 bytes plugins/Dbx_mdbx/res/version.rc | 55 ++++ plugins/Dbx_mdbx/src/dbcontacts.cpp | 247 +++++++++++++++++ plugins/Dbx_mdbx/src/dbcrypt.cpp | 226 ++++++++++++++++ plugins/Dbx_mdbx/src/dbevents.cpp | 428 ++++++++++++++++++++++++++++++ plugins/Dbx_mdbx/src/dbintf.cpp | 279 +++++++++++++++++++ plugins/Dbx_mdbx/src/dbintf.h | 268 +++++++++++++++++++ plugins/Dbx_mdbx/src/dbmodulechain.cpp | 72 +++++ plugins/Dbx_mdbx/src/dbsettings.cpp | 398 +++++++++++++++++++++++++++ plugins/Dbx_mdbx/src/dbutils.cpp | 47 ++++ plugins/Dbx_mdbx/src/init.cpp | 133 ++++++++++ plugins/Dbx_mdbx/src/libmdbx | 1 + plugins/Dbx_mdbx/src/resource.h | 35 +++ plugins/Dbx_mdbx/src/stdafx.cxx | 18 ++ plugins/Dbx_mdbx/src/stdafx.h | 169 ++++++++++++ plugins/Dbx_mdbx/src/ui.cpp | 202 ++++++++++++++ plugins/Dbx_mdbx/src/ui.h | 187 +++++++++++++ plugins/Dbx_mdbx/src/version.h | 13 + 22 files changed, 3010 insertions(+) create mode 100644 plugins/Dbx_mdbx/dbx_mdbx.vcxproj create mode 100644 plugins/Dbx_mdbx/dbx_mdbx.vcxproj.filters create mode 100644 plugins/Dbx_mdbx/res/dbx_mdbx.rc create mode 100644 plugins/Dbx_mdbx/res/logo.ico create mode 100644 plugins/Dbx_mdbx/res/pass.ico create mode 100644 plugins/Dbx_mdbx/res/version.rc create mode 100644 plugins/Dbx_mdbx/src/dbcontacts.cpp create mode 100644 plugins/Dbx_mdbx/src/dbcrypt.cpp create mode 100644 plugins/Dbx_mdbx/src/dbevents.cpp create mode 100644 plugins/Dbx_mdbx/src/dbintf.cpp create mode 100644 plugins/Dbx_mdbx/src/dbintf.h create mode 100644 plugins/Dbx_mdbx/src/dbmodulechain.cpp create mode 100644 plugins/Dbx_mdbx/src/dbsettings.cpp create mode 100644 plugins/Dbx_mdbx/src/dbutils.cpp create mode 100644 plugins/Dbx_mdbx/src/init.cpp create mode 160000 plugins/Dbx_mdbx/src/libmdbx create mode 100644 plugins/Dbx_mdbx/src/resource.h create mode 100644 plugins/Dbx_mdbx/src/stdafx.cxx create mode 100644 plugins/Dbx_mdbx/src/stdafx.h create mode 100644 plugins/Dbx_mdbx/src/ui.cpp create mode 100644 plugins/Dbx_mdbx/src/ui.h create mode 100644 plugins/Dbx_mdbx/src/version.h (limited to 'plugins/Dbx_mdbx') diff --git a/plugins/Dbx_mdbx/dbx_mdbx.vcxproj b/plugins/Dbx_mdbx/dbx_mdbx.vcxproj new file mode 100644 index 0000000000..54c0214bd5 --- /dev/null +++ b/plugins/Dbx_mdbx/dbx_mdbx.vcxproj @@ -0,0 +1,51 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + Dbx_mdbx + {E0ACDEA0-0AC9-4431-8CA3-6B0CCACB2E18} + + + + + + + NotUsing + + + NotUsing + + + NotUsing + + + NotUsing + + + + + + + + Sync + MDB_DEBUG=5;%(PreprocessorDefinitions) + + + \ No newline at end of file diff --git a/plugins/Dbx_mdbx/dbx_mdbx.vcxproj.filters b/plugins/Dbx_mdbx/dbx_mdbx.vcxproj.filters new file mode 100644 index 0000000000..de5ad9f66c --- /dev/null +++ b/plugins/Dbx_mdbx/dbx_mdbx.vcxproj.filters @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/plugins/Dbx_mdbx/res/dbx_mdbx.rc b/plugins/Dbx_mdbx/res/dbx_mdbx.rc new file mode 100644 index 0000000000..03c0486fe6 --- /dev/null +++ b/plugins/Dbx_mdbx/res/dbx_mdbx.rc @@ -0,0 +1,177 @@ +// Microsoft Visual C++ generated resource script. +// +#include "..\src\resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Английский (США) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_SELECT_CRYPTOPROVIDER DIALOGEX 0, 0, 229, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Select crypto provider" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,113,55,50,14 + PUSHBUTTON "Cancel",IDCANCEL,172,55,50,14 + COMBOBOX IDC_SELECTCRYPT_COMBO,16,12,199,30,CBS_DROPDOWNLIST | CBS_SORT | WS_TABSTOP + LTEXT "",IDC_CRYPTOPROVIDER_DESCR,17,29,197,14,NOT WS_GROUP + CONTROL "Total encryption (Recommended only for paranoid users)",IDC_CHECK_TOTALCRYPT, + "Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,14,45,89,24 +END + +IDD_LOGIN DIALOGEX 0, 0, 190, 86 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOPMOST | WS_EX_TOOLWINDOW +CAPTION "Login to Miranda NG" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_HEADERBAR,"MHeaderbarCtrl",0x0,0,0,190,26 + CTEXT "",IDC_LANG,158,34,13,13,SS_CENTERIMAGE | NOT WS_GROUP + EDITTEXT IDC_USERPASS,21,34,128,14,ES_PASSWORD | ES_AUTOHSCROLL | WS_GROUP + DEFPUSHBUTTON "OK",IDOK,36,64,50,14 + PUSHBUTTON "Cancel",IDCANCEL,102,64,50,14 + CONTROL "",IDC_STATIC,"Static",SS_ETCHEDFRAME,0,55,190,1 +END + +IDD_NEWPASS DIALOGEX 0, 0, 190, 102 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOPMOST | WS_EX_TOOLWINDOW +CAPTION "New password" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Please enter your new password",IDC_HEADERBAR, + "MHeaderbarCtrl",0x0,0,0,190,26 + CTEXT "",IDC_LANG,158,34,13,13,SS_CENTERIMAGE | NOT WS_GROUP + EDITTEXT IDC_USERPASS1,21,34,128,14,ES_PASSWORD | ES_AUTOHSCROLL + EDITTEXT IDC_USERPASS2,21,54,128,14,ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,36,84,50,14 + PUSHBUTTON "Cancel",IDCANCEL,100,84,50,14 + CONTROL "",IDC_STATIC,"Static",SS_ETCHEDFRAME,0,77,190,1 +END + +IDD_CHANGEPASS DIALOGEX 0, 0, 190, 148 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOOLWINDOW +CAPTION "Enter password" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Change password",IDC_HEADERBAR,"MHeaderbarCtrl",0x0,0,0,190,26 + CTEXT "",IDC_LANG,158,42,13,13,SS_CENTERIMAGE | NOT WS_GROUP + EDITTEXT IDC_OLDPASS,21,42,128,14,ES_PASSWORD | ES_AUTOHSCROLL + EDITTEXT IDC_USERPASS1,21,77,128,14,ES_PASSWORD | ES_AUTOHSCROLL + EDITTEXT IDC_USERPASS2,21,98,128,14,ES_PASSWORD | ES_AUTOHSCROLL + DEFPUSHBUTTON "Change",IDOK,11,127,50,14 + PUSHBUTTON "Remove",IDREMOVE,69,127,50,14 + PUSHBUTTON "Cancel",IDCANCEL,126,127,50,14 + LTEXT "New password",IDC_STATIC,11,66,163,10,0,WS_EX_TRANSPARENT + CONTROL "",IDC_STATIC,"Static",SS_ETCHEDFRAME,0,119,190,1 + LTEXT "Old password",IDC_STATIC,11,31,140,10,0,WS_EX_TRANSPARENT +END + +IDD_OPTIONS DIALOGEX 0, 0, 318, 176 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + GROUPBOX "Database encryption mode",IDC_STATIC,6,22,305,125 + CONTROL "Standard",IDC_STANDARD,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,12,38,292,12 + CONTROL "Total",IDC_TOTAL,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,12,95,292,12 + LTEXT "Only critical data are encrypted (passwords, security tokens, etc). All other settings and history remains unencrypted. Fast and effective, suitable for the most cases",IDC_STATIC,22,54,284,37 + LTEXT "All string settings and all events in histories are encrypted. It also makes Miranda much slower and creates a risk of losing everything you've stored in a profile in case of losing password. Recommended only for paranoid users",IDC_STATIC,22,110,284,33 + PUSHBUTTON "Set password",IDC_USERPASS,200,153,111,17 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICONPASS ICON "pass.ico" +IDI_LOGO ICON "logo.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_SELECT_CRYPTOPROVIDER, DIALOG + BEGIN + END + + IDD_LOGIN, DIALOG + BEGIN + END + + IDD_CHANGEPASS, DIALOG + BEGIN + END + + IDD_OPTIONS, DIALOG + BEGIN + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "..\\src\\resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include \0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // Английский (США) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/Dbx_mdbx/res/logo.ico b/plugins/Dbx_mdbx/res/logo.ico new file mode 100644 index 0000000000..f49bbe83d6 Binary files /dev/null and b/plugins/Dbx_mdbx/res/logo.ico differ diff --git a/plugins/Dbx_mdbx/res/pass.ico b/plugins/Dbx_mdbx/res/pass.ico new file mode 100644 index 0000000000..dc47a6ed4f Binary files /dev/null and b/plugins/Dbx_mdbx/res/pass.ico differ diff --git a/plugins/Dbx_mdbx/res/version.rc b/plugins/Dbx_mdbx/res/version.rc new file mode 100644 index 0000000000..fdeb14668c --- /dev/null +++ b/plugins/Dbx_mdbx/res/version.rc @@ -0,0 +1,55 @@ +// Microsoft Visual C++ generated resource script. +// +#ifdef APSTUDIO_INVOKED +#error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + +#include "..\src\version.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "afxres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +VS_VERSION_INFO VERSIONINFO + FILEVERSION __FILEVERSION_STRING + PRODUCTVERSION __FILEVERSION_STRING + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "Author", __AUTHOR + VALUE "FileDescription", __DESCRIPTION + VALUE "FileVersion", __VERSION_STRING + VALUE "InternalName", __PLUGIN_NAME + VALUE "LegalCopyright", __COPYRIGHT + VALUE "OriginalFilename", __FILENAME + VALUE "ProductName", __PLUGIN_NAME + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// diff --git a/plugins/Dbx_mdbx/src/dbcontacts.cpp b/plugins/Dbx_mdbx/src/dbcontacts.cpp new file mode 100644 index 0000000000..1195c0af6e --- /dev/null +++ b/plugins/Dbx_mdbx/src/dbcontacts.cpp @@ -0,0 +1,247 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (c) 2012-18 Miranda NG team (https://miranda-ng.org) +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" + +STDMETHODIMP_(LONG) CDbxMDBX::GetContactCount(void) +{ + return m_contactCount; +} + +STDMETHODIMP_(LONG) CDbxMDBX::GetContactSize(void) +{ + return sizeof(DBCachedContact); +} + +STDMETHODIMP_(LONG) CDbxMDBX::DeleteContact(MCONTACT contactID) +{ + if (contactID == 0) // global contact cannot be removed + return 1; + + NotifyEventHooks(hContactDeletedEvent, contactID, 0); + { + OBJLIST events(50); + GatherContactHistory(contactID, events); + while (events.getCount()) { + DeleteEvent(contactID, events[0].eventId); + events.remove(0); + } + } + { + MDBX_val key, data; + DBSettingKey keyS = { contactID, 0, 0 }; + + txn_ptr txn(m_env); + cursor_ptr cursor(txn, m_dbSettings); + + key.iov_len = sizeof(keyS); key.iov_base = &keyS; + + for (int res = mdbx_cursor_get(cursor, &key, &data, MDBX_SET_RANGE); res == MDBX_SUCCESS; res = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) { + const DBSettingKey *pKey = (const DBSettingKey*)key.iov_base; + if (pKey->hContact != contactID) + break; + mdbx_cursor_del(cursor, 0); + } + + txn.commit(); + } + + MDBX_val key = { &contactID, sizeof(MCONTACT) }; + for (;; Remap()) { + txn_ptr trnlck(m_env); + MDBX_CHECK(mdbx_del(trnlck, m_dbContacts, &key, nullptr), 1); + if (trnlck.commit() == MDBX_SUCCESS) + break; + } + + InterlockedDecrement(&m_contactCount); + + return 0; +} + +STDMETHODIMP_(MCONTACT) CDbxMDBX::AddContact() +{ + MCONTACT dwContactId = InterlockedIncrement(&m_maxContactId); + + DBCachedContact *cc = m_cache->AddContactToCache(dwContactId); + + MDBX_val key = { &dwContactId, sizeof(MCONTACT) }; + MDBX_val data = { &cc->dbc, sizeof(cc->dbc) }; + + for (;; Remap()) { + txn_ptr trnlck(m_env); + MDBX_CHECK(mdbx_put(trnlck, m_dbContacts, &key, &data, 0), 0); + if (trnlck.commit() == MDBX_SUCCESS) + break; + } + + InterlockedIncrement(&m_contactCount); + NotifyEventHooks(hContactAddedEvent, dwContactId, 0); + return dwContactId; +} + +STDMETHODIMP_(BOOL) CDbxMDBX::IsDbContact(MCONTACT contactID) +{ + DBCachedContact *cc = m_cache->GetCachedContact(contactID); + return (cc != NULL); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDbxMDBX::GatherContactHistory(MCONTACT hContact, LIST &list) +{ + DBEventSortingKey keyVal = { 0, 0, hContact }; + MDBX_val key = { &keyVal, sizeof(keyVal) }, data; + + txn_ptr_ro trnlck(m_txn); + cursor_ptr_ro cursor(m_curEventsSort); + + for (int res = mdbx_cursor_get(cursor, &key, &data, MDBX_SET_RANGE); res == MDBX_SUCCESS; res = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) { + const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base; + if (pKey->hContact != hContact) + return; + + list.insert(new EventItem(pKey->ts, pKey->hEvent)); + } +} + +BOOL CDbxMDBX::MetaMergeHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub) +{ + LIST list(1000); + GatherContactHistory(ccSub->contactID, list); + + for (int i = 0; i < list.getCount(); i++) { + EventItem *EI = list[i]; + + for (;; Remap()) { + txn_ptr trnlck(m_env); + DBEventSortingKey insVal = { EI->eventId, EI->ts, ccMeta->contactID }; + MDBX_val key = { &insVal, sizeof(insVal) }, data = { (void*)"", 1 }; + mdbx_put(trnlck, m_dbEventsSort, &key, &data, 0); + if (trnlck.commit() == MDBX_SUCCESS) + break; + } + ccMeta->dbc.dwEventCount++; + delete EI; + } + + MDBX_val keyc = { &ccMeta->contactID, sizeof(MCONTACT) }, datac = { &ccMeta->dbc, sizeof(ccMeta->dbc) }; + + for (;; Remap()) { + txn_ptr trnlck(m_env); + MDBX_CHECK(mdbx_put(trnlck, m_dbContacts, &keyc, &datac, 0), 1); + if (trnlck.commit() == MDBX_SUCCESS) + break; + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +BOOL CDbxMDBX::MetaSplitHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub) +{ + LIST list(1000); + GatherContactHistory(ccSub->contactID, list); + + for (int i = 0; i < list.getCount(); i++) { + EventItem *EI = list[i]; + + for (;; Remap()) { + txn_ptr trnlck(m_env); + DBEventSortingKey insVal = { EI->eventId, EI->ts, ccMeta->contactID }; + MDBX_val key = { &insVal, sizeof(insVal) }, data = { (void*)"", 1 }; + mdbx_del(trnlck, m_dbEventsSort, &key, &data); + if (trnlck.commit() == MDBX_SUCCESS) + break; + } + ccMeta->dbc.dwEventCount--; + delete EI; + } + + MDBX_val keyc = { &ccMeta->contactID, sizeof(MCONTACT) }, datac = { &ccMeta->dbc, sizeof(ccMeta->dbc) }; + + for (;; Remap()) { + txn_ptr trnlck(m_env); + MDBX_CHECK(mdbx_put(trnlck, m_dbContacts, &keyc, &datac, 0), 1); + if (trnlck.commit() == MDBX_SUCCESS) + break; + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void DBCachedContact::Advance(MEVENT id, DBEvent &dbe) +{ + dbc.dwEventCount++; + + if (dbe.flags & (DBEF_READ | DBEF_SENT)) + return; + + if (dbe.timestamp < dbc.tsFirstUnread || dbc.tsFirstUnread == 0) { + dbc.tsFirstUnread = dbe.timestamp; + dbc.evFirstUnread = id; + } +} + +void DBCachedContact::Snapshot() +{ + tmp_dbc = dbc; +} + +void DBCachedContact::Revert() +{ + dbc = tmp_dbc; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// initial cycle to fill the contacts' cache + +void CDbxMDBX::FillContacts() +{ + LIST arContacts(m_contactCount); + + txn_ptr_ro trnlck(m_txn); + cursor_ptr_ro cursor(m_curContacts); + + MDBX_val key, data; + while (mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT) == MDBX_SUCCESS) { + DBCachedContact *cc = m_cache->AddContactToCache(*(MCONTACT*)key.iov_base); + cc->dbc = *(DBContact*)data.iov_base; + + CheckProto(cc, ""); + + DBVARIANT dbv; dbv.type = DBVT_DWORD; + cc->nSubs = (0 != GetContactSetting(cc->contactID, META_PROTO, "NumContacts", &dbv)) ? -1 : dbv.dVal; + if (cc->nSubs != -1) { + cc->pSubs = (MCONTACT*)mir_alloc(cc->nSubs * sizeof(MCONTACT)); + for (int k = 0; k < cc->nSubs; k++) { + char setting[100]; + mir_snprintf(setting, _countof(setting), "Handle%d", k); + cc->pSubs[k] = (0 != GetContactSetting(cc->contactID, META_PROTO, setting, &dbv)) ? NULL : dbv.dVal; + } + } + cc->nDefault = (0 != GetContactSetting(cc->contactID, META_PROTO, "Default", &dbv)) ? -1 : dbv.dVal; + cc->parentID = (0 != GetContactSetting(cc->contactID, META_PROTO, "ParentMeta", &dbv)) ? NULL : dbv.dVal; + } +} diff --git a/plugins/Dbx_mdbx/src/dbcrypt.cpp b/plugins/Dbx_mdbx/src/dbcrypt.cpp new file mode 100644 index 0000000000..6142a7b577 --- /dev/null +++ b/plugins/Dbx_mdbx/src/dbcrypt.cpp @@ -0,0 +1,226 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (c) 2012-18 Miranda NG team (https://miranda-ng.org) +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" + +///////////////////////////////////////////////////////////////////////////////////////// + +char DBKey_Crypto_Provider[] = "Provider"; +char DBKey_Crypto_Key[] = "Key"; +char DBKey_Crypto_IsEncrypted[] = "EncryptedDB"; + +CRYPTO_PROVIDER* CDbxMDBX::SelectProvider() +{ + CRYPTO_PROVIDER **ppProvs, *pProv; + int iNumProvs; + Crypto_EnumProviders(&iNumProvs, &ppProvs); + + if (iNumProvs == 0) + return nullptr; + + bool bTotalCrypt = false; + + if (iNumProvs > 1) { + CSelectCryptoDialog dlg(ppProvs, iNumProvs); + dlg.DoModal(); + pProv = dlg.GetSelected(); + bTotalCrypt = dlg.TotalSelected(); + } + else pProv = ppProvs[0]; + + for (;; Remap()) { + txn_ptr txn(m_env); + + MDBX_val key = { DBKey_Crypto_Provider, sizeof(DBKey_Crypto_Provider) }, value = { pProv->pszName, mir_strlen(pProv->pszName) + 1 }; + MDBX_CHECK(mdbx_put(txn, m_dbCrypto, &key, &value, 0), nullptr); + + key.iov_len = sizeof(DBKey_Crypto_IsEncrypted); key.iov_base = DBKey_Crypto_IsEncrypted; value.iov_len = sizeof(bool); value.iov_base = &bTotalCrypt; + MDBX_CHECK(mdbx_put(txn, m_dbCrypto, &key, &value, 0), nullptr); + + if (txn.commit() == MDBX_SUCCESS) + break; + } + + return pProv; +} + +int CDbxMDBX::InitCrypt() +{ + CRYPTO_PROVIDER *pProvider; + + txn_ptr_ro txn(m_txn); + + MDBX_val key = { DBKey_Crypto_Provider, sizeof(DBKey_Crypto_Provider) }, value; + if (mdbx_get(txn, m_dbCrypto, &key, &value) == MDBX_SUCCESS) { + pProvider = Crypto_GetProvider((const char*)value.iov_base); + if (pProvider == nullptr) + pProvider = SelectProvider(); + } + else pProvider = SelectProvider(); + + if (pProvider == nullptr) + return 1; + + if ((m_crypto = pProvider->pFactory()) == nullptr) + return 3; + + key.iov_len = sizeof(DBKey_Crypto_Key); key.iov_base = DBKey_Crypto_Key; + if (mdbx_get(txn, m_dbCrypto, &key, &value) == MDBX_SUCCESS && (value.iov_len == m_crypto->getKeyLength())) { + if (!m_crypto->setKey((const BYTE*)value.iov_base, value.iov_len)) { + DlgChangePassParam param = { this }; + CEnterPasswordDialog dlg(¶m); + while (true) { + if (-128 != dlg.DoModal()) + return 4; + m_crypto->setPassword(pass_ptrA(mir_utf8encodeW(param.newPass))); + if (m_crypto->setKey((const BYTE*)value.iov_base, value.iov_len)) { + m_bUsesPassword = true; + SecureZeroMemory(¶m, sizeof(param)); + break; + } + param.wrongPass++; + } + } + } + else { + if (!m_crypto->generateKey()) + return 6; + StoreKey(); + } + + key.iov_len = sizeof(DBKey_Crypto_IsEncrypted); key.iov_base = DBKey_Crypto_IsEncrypted; + + if (mdbx_get(txn, m_dbCrypto, &key, &value) == MDBX_SUCCESS) + m_bEncrypted = *(const bool*)value.iov_base; + else + m_bEncrypted = false; + + InitDialogs(); + return 0; +} + +void CDbxMDBX::StoreKey() +{ + size_t iKeyLength = m_crypto->getKeyLength(); + BYTE *pKey = (BYTE*)_alloca(iKeyLength); + m_crypto->getKey(pKey, iKeyLength); + + for (;; Remap()) { + txn_ptr txn(m_env); + MDBX_val key = { DBKey_Crypto_Key, sizeof(DBKey_Crypto_Key) }, value = { pKey, iKeyLength }; + mdbx_put(txn, m_dbCrypto, &key, &value, 0); + if (txn.commit() == MDBX_SUCCESS) + break; + } + SecureZeroMemory(pKey, iKeyLength); +} + +void CDbxMDBX::SetPassword(const wchar_t *ptszPassword) +{ + if (ptszPassword == NULL || *ptszPassword == 0) { + m_bUsesPassword = false; + m_crypto->setPassword(NULL); + } + else { + m_bUsesPassword = true; + m_crypto->setPassword(pass_ptrA(mir_utf8encodeW(ptszPassword))); + } + UpdateMenuItem(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int CDbxMDBX::EnableEncryption(bool bEncrypted) +{ + if (m_bEncrypted == bEncrypted) + return 0; + + { + txn_ptr_ro txnro(m_txn); + + MDBX_stat st; + mdbx_dbi_stat(txnro, m_dbEvents, &st, sizeof(st)); + + std::vector lstEvents; + lstEvents.reserve(st.ms_entries); + + { + cursor_ptr_ro cursor(m_curEvents); + MDBX_val key, data; + while (mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT) == MDBX_SUCCESS) { + const MEVENT hDbEvent = *(const MEVENT*)key.iov_base; + lstEvents.push_back(hDbEvent); + } + } + for (auto it = lstEvents.begin(); it != lstEvents.end(); ++it) { + MEVENT &hDbEvent = *it; + MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; + mdbx_get(txnro, m_dbEvents, &key, &data); + + const DBEvent *dbEvent = (const DBEvent*)data.iov_base; + const BYTE *pBlob = (BYTE*)(dbEvent + 1); + + if (((dbEvent->flags & DBEF_ENCRYPTED) != 0) != bEncrypted) { + mir_ptr pNewBlob; + size_t nNewBlob; + uint32_t dwNewFlags; + + if (dbEvent->flags & DBEF_ENCRYPTED) { + pNewBlob = (BYTE*)m_crypto->decodeBuffer(pBlob, dbEvent->cbBlob, &nNewBlob); + dwNewFlags = dbEvent->flags & (~DBEF_ENCRYPTED); + } + else { + pNewBlob = m_crypto->encodeBuffer(pBlob, dbEvent->cbBlob, &nNewBlob); + dwNewFlags = dbEvent->flags | DBEF_ENCRYPTED; + } + + for (;; Remap()) { + txn_ptr txn(m_env); + data.iov_len = sizeof(DBEvent) + nNewBlob; + MDBX_CHECK(mdbx_put(txn, m_dbEvents, &key, &data, MDBX_RESERVE), 1); + + DBEvent *pNewDBEvent = (DBEvent *)data.iov_base; + *pNewDBEvent = *dbEvent; + pNewDBEvent->cbBlob = (uint16_t)nNewBlob; + pNewDBEvent->flags = dwNewFlags; + memcpy(pNewDBEvent + 1, pNewBlob, nNewBlob); + + + if (txn.commit() == MDBX_SUCCESS) + break; + } + } + } + } + + for (;; Remap()) { + txn_ptr txn(m_env); + MDBX_val key = { DBKey_Crypto_IsEncrypted, sizeof(DBKey_Crypto_IsEncrypted) }, value = { &bEncrypted, sizeof(bool) }; + MDBX_CHECK(mdbx_put(txn, m_dbCrypto, &key, &value, 0), 1); + if (txn.commit() == MDBX_SUCCESS) + break; + } + + m_bEncrypted = bEncrypted; + return 0; +} diff --git a/plugins/Dbx_mdbx/src/dbevents.cpp b/plugins/Dbx_mdbx/src/dbevents.cpp new file mode 100644 index 0000000000..3ad663a5a9 --- /dev/null +++ b/plugins/Dbx_mdbx/src/dbevents.cpp @@ -0,0 +1,428 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (c) 2012-18 Miranda NG team (https://miranda-ng.org) +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" + +STDMETHODIMP_(LONG) CDbxMDBX::GetEventCount(MCONTACT contactID) +{ + DBCachedContact *cc = m_cache->GetCachedContact(contactID); + return (cc == NULL) ? 0 : cc->dbc.dwEventCount; +} + +STDMETHODIMP_(MEVENT) CDbxMDBX::AddEvent(MCONTACT contactID, DBEVENTINFO *dbei) +{ + if (dbei == NULL) return 0; + if (dbei->timestamp == 0) return 0; + + DBEvent dbe; + dbe.contactID = contactID; // store native or subcontact's id + dbe.iModuleId = GetModuleID(dbei->szModule); + + MCONTACT contactNotifyID = contactID; + DBCachedContact *cc, *ccSub = NULL; + if ((cc = m_cache->GetCachedContact(contactID)) == NULL) + return 0; + + if (cc->IsSub()) { + ccSub = cc; + if ((cc = m_cache->GetCachedContact(cc->parentID)) == NULL) + return 0; + + // set default sub to the event's source + if (!(dbei->flags & DBEF_SENT)) + db_mc_setDefault(cc->contactID, contactID, false); + contactID = cc->contactID; // and add an event to a metahistory + if (db_mc_isEnabled()) + contactNotifyID = contactID; + } + + if (m_safetyMode) + if (NotifyEventHooks(hEventFilterAddedEvent, contactNotifyID, (LPARAM)dbei)) + return NULL; + + dbe.timestamp = dbei->timestamp; + dbe.flags = dbei->flags; + dbe.wEventType = dbei->eventType; + dbe.cbBlob = dbei->cbBlob; + BYTE *pBlob = dbei->pBlob; + + mir_ptr pCryptBlob; + if (m_bEncrypted) { + size_t len; + BYTE *pResult = m_crypto->encodeBuffer(pBlob, dbe.cbBlob, &len); + if (pResult != NULL) { + pCryptBlob = pBlob = pResult; + dbe.cbBlob = (uint16_t)len; + dbe.flags |= DBEF_ENCRYPTED; + } + } + + + MEVENT dwEventId = InterlockedIncrement(&m_dwMaxEventId); + + const auto Snapshot = [&]() { cc->Snapshot(); if (ccSub) ccSub->Snapshot(); }; + const auto Revert = [&]() { cc->Revert(); if (ccSub) ccSub->Revert(); }; + + for (Snapshot();; Revert(), Remap()) { + txn_ptr txn(m_env); + + MDBX_val key = { &dwEventId, sizeof(MEVENT) }, data = { NULL, sizeof(DBEvent) + dbe.cbBlob }; + MDBX_CHECK(mdbx_put(txn, m_dbEvents, &key, &data, MDBX_RESERVE), 0); + + DBEvent *pNewEvent = (DBEvent*)data.iov_base; + *pNewEvent = dbe; + memcpy(pNewEvent + 1, pBlob, dbe.cbBlob); + + // add a sorting key + DBEventSortingKey key2 = { contactID, dwEventId, dbe.timestamp }; + key.iov_len = sizeof(key2); key.iov_base = &key2; + data.iov_len = 1; data.iov_base = (char*)(""); + MDBX_CHECK(mdbx_put(txn, m_dbEventsSort, &key, &data, 0), 0); + + cc->Advance(dwEventId, dbe); + MDBX_val keyc = { &contactID, sizeof(MCONTACT) }, datac = { &cc->dbc, sizeof(DBContact) }; + MDBX_CHECK(mdbx_put(txn, m_dbContacts, &keyc, &datac, 0), 0); + + // insert an event into a sub's history too + if (ccSub != NULL) { + key2.hContact = ccSub->contactID; + MDBX_CHECK(mdbx_put(txn, m_dbEventsSort, &key, &data, 0), 0); + + ccSub->Advance(dwEventId, dbe); + datac.iov_base = &ccSub->dbc; + keyc.iov_base = &ccSub->contactID; + MDBX_CHECK(mdbx_put(txn, m_dbContacts, &keyc, &datac, 0), 0); + } + + if (txn.commit() == MDBX_SUCCESS) + break; + } + + // Notify only in safe mode or on really new events + if (m_safetyMode) + NotifyEventHooks(hEventAddedEvent, contactNotifyID, dwEventId); + + return dwEventId; +} + +STDMETHODIMP_(BOOL) CDbxMDBX::DeleteEvent(MCONTACT contactID, MEVENT hDbEvent) +{ + DBCachedContact *cc = m_cache->GetCachedContact(contactID), *cc2 = nullptr; + if (cc == NULL || cc->dbc.dwEventCount == 0) + return 1; + + DBEvent dbe; + { + txn_ptr_ro txn(m_txn); + MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; + if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS) + return 1; + dbe = *(DBEvent*)data.iov_base; + } + + if (contactID != dbe.contactID) { + cc2 = m_cache->GetCachedContact(dbe.contactID); + } + + const auto Snapshot = [&]() { cc->Snapshot(); if (cc2) cc2->Snapshot(); }; + const auto Revert = [&]() { cc->Revert(); if (cc2) cc2->Revert(); }; + + for (Snapshot();; Revert(), Remap()) { + DBEventSortingKey key2 = { contactID, hDbEvent, dbe.timestamp }; + + txn_ptr txn(m_env); + MDBX_val key = { &key2, sizeof(key2) }, data; + + MDBX_CHECK(mdbx_del(txn, m_dbEventsSort, &key, &data), 1) + + { + key.iov_len = sizeof(MCONTACT); key.iov_base = &contactID; + cc->dbc.dwEventCount--; + if (cc->dbc.evFirstUnread == hDbEvent) + FindNextUnread(txn, cc, key2); + + data.iov_len = sizeof(DBContact); data.iov_base = &cc->dbc; + MDBX_CHECK(mdbx_put(txn, m_dbContacts, &key, &data, 0), 1); + } + + if (cc2) { + key2.hContact = dbe.contactID; + MDBX_CHECK(mdbx_del(txn, m_dbEventsSort, &key, &data), 1); + + key.iov_len = sizeof(MCONTACT); key.iov_base = &contactID; + cc2->dbc.dwEventCount--; + if (cc2->dbc.evFirstUnread == hDbEvent) + FindNextUnread(txn, cc2, key2); + + data.iov_len = sizeof(DBContact); data.iov_base = &cc2->dbc; + MDBX_CHECK(mdbx_put(txn, m_dbContacts, &key, &data, 0), 1); + + } + + // remove a event + key.iov_len = sizeof(MEVENT); key.iov_base = &hDbEvent; + MDBX_CHECK(mdbx_del(txn, m_dbEvents, &key, &data), 1); + + if (txn.commit() == MDBX_SUCCESS) + break; + } + + NotifyEventHooks(hEventDeletedEvent, contactID, hDbEvent); + + return 0; +} + +STDMETHODIMP_(LONG) CDbxMDBX::GetBlobSize(MEVENT hDbEvent) +{ + txn_ptr_ro txn(m_txn); + + MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; + if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS) + return -1; + return ((const DBEvent*)data.iov_base)->cbBlob; +} + +STDMETHODIMP_(BOOL) CDbxMDBX::GetEvent(MEVENT hDbEvent, DBEVENTINFO *dbei) +{ + if (dbei == NULL) return 1; + if (dbei->cbBlob > 0 && dbei->pBlob == NULL) { + dbei->cbBlob = 0; + return 1; + } + + txn_ptr_ro txn(m_txn); + + MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; + if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS) + return 1; + + const DBEvent *dbe = (const DBEvent*)data.iov_base; + + dbei->szModule = GetModuleName(dbe->iModuleId); + dbei->timestamp = dbe->timestamp; + dbei->flags = dbe->flags; + dbei->eventType = dbe->wEventType; + size_t bytesToCopy = min(dbei->cbBlob, dbe->cbBlob); + dbei->cbBlob = dbe->cbBlob; + if (bytesToCopy && dbei->pBlob) { + BYTE *pSrc = (BYTE*)data.iov_base + sizeof(DBEvent); + if (dbe->flags & DBEF_ENCRYPTED) { + dbei->flags &= ~DBEF_ENCRYPTED; + size_t len; + BYTE* pBlob = (BYTE*)m_crypto->decodeBuffer(pSrc, dbe->cbBlob, &len); + if (pBlob == NULL) + return 1; + + memcpy(dbei->pBlob, pBlob, bytesToCopy); + if (bytesToCopy > len) + memset(dbei->pBlob + len, 0, bytesToCopy - len); + mir_free(pBlob); + } + else memcpy(dbei->pBlob, pSrc, bytesToCopy); + } + return 0; +} + +void CDbxMDBX::FindNextUnread(const txn_ptr &txn, DBCachedContact *cc, DBEventSortingKey &key2) +{ + cursor_ptr cursor(txn, m_dbEventsSort); + + MDBX_val key = { &key2, sizeof(key2) }, data; + + for (int res = mdbx_cursor_get(cursor, &key, &data, MDBX_SET); res == MDBX_SUCCESS; res = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) { + const DBEvent *dbe = (const DBEvent*)data.iov_base; + if (dbe->contactID != cc->contactID) + break; + if (!dbe->markedRead()) { + cc->dbc.evFirstUnread = key2.hEvent; + cc->dbc.tsFirstUnread = key2.ts; + return; + } + } + + cc->dbc.evFirstUnread = cc->dbc.tsFirstUnread = 0; +} + +STDMETHODIMP_(BOOL) CDbxMDBX::MarkEventRead(MCONTACT contactID, MEVENT hDbEvent) +{ + if (hDbEvent == 0) return -1; + + DBCachedContact *cc = m_cache->GetCachedContact(contactID); + if (cc == NULL) + return -1; + + uint32_t wRetVal = -1; + + for (cc->Snapshot();; cc->Revert(), Remap()) { + txn_ptr txn(m_env); + + MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; + MDBX_CHECK(mdbx_get(txn, m_dbEvents, &key, &data), -1); + + const DBEvent *cdbe = (const DBEvent*)data.iov_base; + + if (cdbe->markedRead()) + return cdbe->flags; + + DBEventSortingKey keyVal = { contactID, hDbEvent, cdbe->timestamp }; + + MDBX_CHECK(mdbx_put(txn, m_dbEvents, &key, &data, MDBX_RESERVE), -1); + + DBEvent *pNewEvent = (DBEvent*)data.iov_base; + *pNewEvent = *cdbe; + + wRetVal = (pNewEvent->flags |= DBEF_READ); + + FindNextUnread(txn, cc, keyVal); + key.iov_len = sizeof(MCONTACT); key.iov_base = &contactID; + data.iov_base = &cc->dbc; data.iov_len = sizeof(cc->dbc); + MDBX_CHECK(mdbx_put(txn, m_dbContacts, &key, &data, 0), -1); + + if (txn.commit() == MDBX_SUCCESS) + break; + } + + NotifyEventHooks(hEventMarkedRead, contactID, (LPARAM)hDbEvent); + return wRetVal; +} + +STDMETHODIMP_(MCONTACT) CDbxMDBX::GetEventContact(MEVENT hDbEvent) +{ + if (hDbEvent == 0) + return INVALID_CONTACT_ID; + + txn_ptr_ro txn(m_txn); + + MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; + if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS) + return INVALID_CONTACT_ID; + + return ((const DBEvent*)data.iov_base)->contactID; +} + +thread_local uint64_t t_tsLast = 0; +thread_local MEVENT t_evLast = 0; + +STDMETHODIMP_(MEVENT) CDbxMDBX::FindFirstEvent(MCONTACT contactID) +{ + DBEventSortingKey keyVal = { contactID, 0, 0 }; + MDBX_val key = { &keyVal, sizeof(keyVal) }, data; + + txn_ptr_ro txn(m_txn); + + cursor_ptr_ro cursor(m_curEventsSort); + if (mdbx_cursor_get(cursor, &key, &data, MDBX_SET_RANGE) != MDBX_SUCCESS) + return t_evLast = 0; + + const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base; + t_tsLast = pKey->ts; + return t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0; +} + +STDMETHODIMP_(MEVENT) CDbxMDBX::FindFirstUnreadEvent(MCONTACT contactID) +{ + DBCachedContact *cc = m_cache->GetCachedContact(contactID); + return (cc == NULL) ? 0 : cc->dbc.evFirstUnread; +} + +STDMETHODIMP_(MEVENT) CDbxMDBX::FindLastEvent(MCONTACT contactID) +{ + DBEventSortingKey keyVal = { contactID, 0xFFFFFFFF, 0xFFFFFFFFFFFFFFFF }; + MDBX_val key = { &keyVal, sizeof(keyVal) }, data; + + txn_ptr_ro txn(m_txn); + cursor_ptr_ro cursor(m_curEventsSort); + + if (mdbx_cursor_get(cursor, &key, &data, MDBX_SET_RANGE) != MDBX_SUCCESS) { + if (mdbx_cursor_get(cursor, &key, &data, MDBX_LAST) != MDBX_SUCCESS) + return t_evLast = 0; + } + else { + if (mdbx_cursor_get(cursor, &key, &data, MDBX_PREV) != MDBX_SUCCESS) + return t_evLast = 0; + } + + const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base; + t_tsLast = pKey->ts; + return t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0; +} + +STDMETHODIMP_(MEVENT) CDbxMDBX::FindNextEvent(MCONTACT contactID, MEVENT hDbEvent) +{ + if (hDbEvent == 0) + return t_evLast = 0; + + txn_ptr_ro txn(m_txn); + + if (t_evLast != hDbEvent) { + MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; + if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS) + return 0; + t_tsLast = ((DBEvent*)data.iov_base)->timestamp; + } + + DBEventSortingKey keyVal = { contactID, hDbEvent, t_tsLast }; + MDBX_val key = { &keyVal, sizeof(keyVal) }, data; + + cursor_ptr_ro cursor(m_curEventsSort); + if (mdbx_cursor_get(cursor, &key, &data, MDBX_SET) != MDBX_SUCCESS) + return t_evLast = 0; + + if (mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT) != MDBX_SUCCESS) + return t_evLast = 0; + + const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base; + t_tsLast = pKey->ts; + return t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0; +} + +STDMETHODIMP_(MEVENT) CDbxMDBX::FindPrevEvent(MCONTACT contactID, MEVENT hDbEvent) +{ + if (hDbEvent == 0) + return t_evLast = 0; + + MDBX_val data; + + txn_ptr_ro txn(m_txn); + + if (t_evLast != hDbEvent) { + MDBX_val key = { &hDbEvent, sizeof(MEVENT) }; + if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS) + return 0; + t_tsLast = ((DBEvent*)data.iov_base)->timestamp; + } + + DBEventSortingKey keyVal = { contactID, hDbEvent, t_tsLast }; + MDBX_val key = { &keyVal, sizeof(keyVal) }; + + cursor_ptr_ro cursor(m_curEventsSort); + if (mdbx_cursor_get(cursor, &key, &data, MDBX_SET) != MDBX_SUCCESS) + return t_evLast = 0; + + if (mdbx_cursor_get(cursor, &key, &data, MDBX_PREV) != MDBX_SUCCESS) + return t_evLast = 0; + + const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base; + t_tsLast = pKey->ts; + return t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0; +} diff --git a/plugins/Dbx_mdbx/src/dbintf.cpp b/plugins/Dbx_mdbx/src/dbintf.cpp new file mode 100644 index 0000000000..09d350b6c9 --- /dev/null +++ b/plugins/Dbx_mdbx/src/dbintf.cpp @@ -0,0 +1,279 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (c) 2012-18 Miranda NG team (https://miranda-ng.org) +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" + +CDbxMDBX::CDbxMDBX(const TCHAR *tszFileName, int iMode) : + m_safetyMode(true), + m_bReadOnly((iMode & DBMODE_READONLY) != 0), + m_bShared((iMode & DBMODE_SHARED) != 0), + m_maxContactId(0) +{ + m_tszProfileName = mir_wstrdup(tszFileName); + InitDbInstance(this); + + mdbx_env_create(&m_env); + mdbx_env_set_maxdbs(m_env, 10); + mdbx_env_set_userctx(m_env, this); + // mdbx_env_set_assert(m_env, MDBX_FailAssert); +} + +CDbxMDBX::~CDbxMDBX() +{ + mdbx_env_close(m_env); + + DestroyServiceFunction(hService); + UnhookEvent(hHook); + + if (m_crypto) + m_crypto->destroy(); + + DestroyHookableEvent(hContactDeletedEvent); + DestroyHookableEvent(hContactAddedEvent); + DestroyHookableEvent(hSettingChangeEvent); + DestroyHookableEvent(hEventMarkedRead); + + DestroyHookableEvent(hEventAddedEvent); + DestroyHookableEvent(hEventDeletedEvent); + DestroyHookableEvent(hEventFilterAddedEvent); + + DestroyDbInstance(this); + mir_free(m_tszProfileName); +} + +int CDbxMDBX::Load(bool bSkipInit) +{ + if (Map() != MDBX_SUCCESS) + return EGROKPRF_CANTREAD; + + if (!bSkipInit) { + txn_ptr trnlck(m_env); + + unsigned int defFlags = MDBX_CREATE; + + mdbx_dbi_open(trnlck, "global", defFlags | MDBX_INTEGERKEY, &m_dbGlobal); + mdbx_dbi_open(trnlck, "crypto", defFlags, &m_dbCrypto); + mdbx_dbi_open(trnlck, "contacts", defFlags | MDBX_INTEGERKEY, &m_dbContacts); + mdbx_dbi_open(trnlck, "modules", defFlags | MDBX_INTEGERKEY, &m_dbModules); + mdbx_dbi_open(trnlck, "events", defFlags | MDBX_INTEGERKEY, &m_dbEvents); + + mdbx_dbi_open_ex(trnlck, "eventsrt", defFlags, &m_dbEventsSort, DBEventSortingKey::Compare, nullptr); + mdbx_dbi_open_ex(trnlck, "settings", defFlags, &m_dbSettings, DBSettingKey::Compare, nullptr); + { + uint32_t keyVal = 1; + MDBX_val key = { &keyVal, sizeof(keyVal) }, data; + if (mdbx_get(trnlck, m_dbGlobal, &key, &data) == MDBX_SUCCESS) { + const DBHeader *hdr = (const DBHeader*)data.iov_base; + if (hdr->dwSignature != DBHEADER_SIGNATURE) + return EGROKPRF_DAMAGED; + if (hdr->dwVersion != DBHEADER_VERSION) + return EGROKPRF_OBSOLETE; + + m_header = *hdr; + } + else { + m_header.dwSignature = DBHEADER_SIGNATURE; + m_header.dwVersion = DBHEADER_VERSION; + data.iov_base = &m_header; data.iov_len = sizeof(m_header); + mdbx_put(trnlck, m_dbGlobal, &key, &data, 0); + } + trnlck.commit(); + } + { + MDBX_val key, val; + + mdbx_txn_begin(m_env, nullptr, MDBX_RDONLY, &m_txn); + + mdbx_cursor_open(m_txn, m_dbEvents, &m_curEvents); + if (mdbx_cursor_get(m_curEvents, &key, &val, MDBX_LAST) == MDBX_SUCCESS) + m_dwMaxEventId = *(MEVENT*)key.iov_base; + + mdbx_cursor_open(m_txn, m_dbEventsSort, &m_curEventsSort); + mdbx_cursor_open(m_txn, m_dbSettings, &m_curSettings); + mdbx_cursor_open(m_txn, m_dbModules, &m_curModules); + + mdbx_cursor_open(m_txn, m_dbContacts, &m_curContacts); + if (mdbx_cursor_get(m_curContacts, &key, &val, MDBX_LAST) == MDBX_SUCCESS) + m_maxContactId = *(MCONTACT*)key.iov_base; + + MDBX_stat st; + mdbx_dbi_stat(m_txn, m_dbContacts, &st, sizeof(st)); + m_contactCount = st.ms_entries; + + mdbx_txn_reset(m_txn); + } + + + if (InitModules()) return EGROKPRF_DAMAGED; + if (InitCrypt()) return EGROKPRF_DAMAGED; + + // everything is ok, go on + if (!m_bReadOnly) { + // retrieve the event handles + hContactDeletedEvent = CreateHookableEvent(ME_DB_CONTACT_DELETED); + hContactAddedEvent = CreateHookableEvent(ME_DB_CONTACT_ADDED); + hSettingChangeEvent = CreateHookableEvent(ME_DB_CONTACT_SETTINGCHANGED); + hEventMarkedRead = CreateHookableEvent(ME_DB_EVENT_MARKED_READ); + + hEventAddedEvent = CreateHookableEvent(ME_DB_EVENT_ADDED); + hEventDeletedEvent = CreateHookableEvent(ME_DB_EVENT_DELETED); + hEventFilterAddedEvent = CreateHookableEvent(ME_DB_EVENT_FILTER_ADD); + } + + FillContacts(); + } + + return EGROKPRF_NOERROR; +} + +int CDbxMDBX::Create(void) +{ + return (Map() == MDBX_SUCCESS) ? 0 : EGROKPRF_CANTREAD; +} + +size_t iDefHeaderOffset = 0; +BYTE bDefHeader[] = { 0 }; + +int CDbxMDBX::Check(void) +{ + FILE *pFile = _wfopen(m_tszProfileName, L"rb"); + if (pFile == nullptr) + return EGROKPRF_CANTREAD; + + fseek(pFile, (LONG)iDefHeaderOffset, SEEK_SET); + BYTE buf[_countof(bDefHeader)]; + size_t cbRead = fread(buf, 1, _countof(buf), pFile); + fclose(pFile); + if (cbRead != _countof(buf)) + return EGROKPRF_DAMAGED; + + return (memcmp(buf, bDefHeader, _countof(bDefHeader))) ? EGROKPRF_UNKHEADER : 0; +} + +int CDbxMDBX::PrepareCheck(int*) +{ + InitModules(); + return InitCrypt(); +} + +STDMETHODIMP_(void) CDbxMDBX::SetCacheSafetyMode(BOOL bIsSet) +{ + m_safetyMode = bIsSet != 0; +} + +int CDbxMDBX::Map() +{ + unsigned int mode = MDBX_NOSUBDIR | MDBX_MAPASYNC | MDBX_WRITEMAP | MDBX_NOSYNC; + if (m_bReadOnly) + mode |= MDBX_RDONLY; + mdbx_env_open(m_env, _T2A(m_tszProfileName), mode, 0664); + mdbx_env_set_mapsize(m_env, 0x1000000); + return MDBX_SUCCESS; +} + +bool CDbxMDBX::Remap() +{ + MDBX_envinfo ei; + mdbx_env_info(m_env, &ei, sizeof(ei)); + return mdbx_env_set_geometry(m_env, -1, -1, ei.mi_mapsize + 0x100000, 0x100000, -1, -1) == MDBX_SUCCESS; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static DWORD DatabaseCorrupted = 0; +static const TCHAR *msg = NULL; +static DWORD dwErr = 0; +static wchar_t tszPanic[] = LPGENW("Miranda has detected corruption in your database. This corruption may be fixed by DbChecker plugin. Please download it from https://miranda-ng.org/p/DbChecker/. Miranda will now shut down."); + +EXTERN_C void __cdecl dbpanic(void *) +{ + if (msg) { + if (dwErr == ERROR_DISK_FULL) + msg = TranslateT("Disk is full. Miranda will now shut down."); + + TCHAR err[256]; + mir_snwprintf(err, msg, TranslateT("Database failure. Miranda will now shut down."), dwErr); + + MessageBox(0, err, TranslateT("Database Error"), MB_SETFOREGROUND | MB_TOPMOST | MB_APPLMODAL | MB_ICONWARNING | MB_OK); + } + else MessageBox(0, TranslateW(tszPanic), TranslateT("Database Panic"), MB_SETFOREGROUND | MB_TOPMOST | MB_APPLMODAL | MB_ICONWARNING | MB_OK); + TerminateProcess(GetCurrentProcess(), 255); +} + + +EXTERN_C void MDBX_FailAssert(MDBX_env *env, const char *text) +{ + ((CDbxMDBX*)mdbx_env_get_userctx(env))->DatabaseCorruption(_A2T(text)); +} + +EXTERN_C void MDBX_Log(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Netlib_Log(0, CMStringA().FormatV(fmt, args)); + va_end(args); +} + +void CDbxMDBX::DatabaseCorruption(const TCHAR *text) +{ + int kill = 0; + + if (DatabaseCorrupted == 0) { + DatabaseCorrupted++; + kill++; + msg = text; + dwErr = GetLastError(); + } + else { + /* db is already corrupted, someone else is dealing with it, wait here + so that we don't do any more damage */ + Sleep(INFINITE); + return; + } + + if (kill) { + _beginthread(dbpanic, 0, NULL); + Sleep(INFINITE); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// MIDatabaseChecker + +typedef int (CDbxMDBX::*CheckWorker)(int); + +int CDbxMDBX::Start(DBCHeckCallback *callback) +{ + cb = callback; + return ERROR_SUCCESS; +} + +int CDbxMDBX::CheckDb(int, int) +{ + return ERROR_OUT_OF_PAPER; +} + +void CDbxMDBX::Destroy() +{ + delete this; +} diff --git a/plugins/Dbx_mdbx/src/dbintf.h b/plugins/Dbx_mdbx/src/dbintf.h new file mode 100644 index 0000000000..fa5595dba3 --- /dev/null +++ b/plugins/Dbx_mdbx/src/dbintf.h @@ -0,0 +1,268 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (c) 2012-18 Miranda NG team (https://miranda-ng.org) +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#define OWN_CACHED_CONTACT + +#include + +#pragma warning (disable: 4200) + +#define DBMODE_SHARED 0x0001 +#define DBMODE_READONLY 0x0002 + +#define DBVT_ENCRYPTED 250 +#define DBVT_UNENCRYPTED 251 + +#define MARKED_READ (DBEF_READ | DBEF_SENT) + +#include + +#define DBHEADER_VERSION MAKELONG(1, 4) + +#define DBHEADER_SIGNATURE 0x40DECADEu +struct DBHeader +{ + uint32_t dwSignature; + uint32_t dwVersion; // database format version +}; + +struct DBContact +{ + uint32_t dwEventCount; // number of events in the chain for this contact + MEVENT evFirstUnread; + uint64_t tsFirstUnread; +}; + +struct DBEvent +{ + MCONTACT contactID; // a contact this event belongs to + uint32_t iModuleId; // offset to a DBModuleName struct of the name of + uint64_t timestamp; // seconds since 00:00:00 01/01/1970 + uint32_t flags; // see m_database.h, db/event/add + uint16_t wEventType; // module-defined event type + uint16_t cbBlob; // number of bytes in the blob + + bool __forceinline markedRead() const + { + return (flags & MARKED_READ) != 0; + } +}; + +struct DBEventSortingKey +{ + MCONTACT hContact; + MEVENT hEvent; + uint64_t ts; + + static int Compare(const MDBX_val* a, const MDBX_val* b); +}; + +struct DBSettingKey +{ + MCONTACT hContact; + uint32_t dwModuleId; + char szSettingName[1]; + + static int Compare(const MDBX_val*, const MDBX_val*); +}; + +struct DBSettingValue +{ + BYTE type; + union + { + BYTE bVal; + WORD wVal; + DWORD dwVal; + char szVal[]; + + struct + { + size_t nLength; + BYTE bVal[]; + } blob; + }; +}; + +#include + +struct DBCachedContact : public DBCachedContactBase +{ + void Advance(MEVENT id, DBEvent &dbe); + void Snapshot(); + void Revert(); + DBContact dbc, tmp_dbc; +}; + +struct EventItem +{ + __forceinline EventItem(int _ts, MEVENT _id) : + ts(_ts), eventId(_id) + {} + + uint64_t ts; + MEVENT eventId; +}; + +struct CDbxMDBX : public MDatabaseCommon, public MIDatabaseChecker, public MZeroedObject +{ + friend class MDBXEventCursor; + + CDbxMDBX(const TCHAR *tszFileName, int mode); + virtual ~CDbxMDBX(); + + int Load(bool bSkipInit); + int Create(void); + int Check(void); + + void DatabaseCorruption(const TCHAR *ptszText); + + void StoreKey(void); + void SetPassword(const wchar_t *ptszPassword); + void UpdateMenuItem(void); + + int PrepareCheck(int*); + + __forceinline LPSTR GetMenuTitle() const { return m_bUsesPassword ? (char*)LPGEN("Change/remove password") : (char*)LPGEN("Set password"); } + + __forceinline bool isEncrypted() const { return m_bEncrypted; } + __forceinline bool usesPassword() const { return m_bUsesPassword; } + int EnableEncryption(bool bEnable); +public: + STDMETHODIMP_(BOOL) IsRelational(void) { return TRUE; } + STDMETHODIMP_(void) SetCacheSafetyMode(BOOL); + + STDMETHODIMP_(LONG) GetContactCount(void); + STDMETHODIMP_(LONG) DeleteContact(MCONTACT contactID); + STDMETHODIMP_(MCONTACT) AddContact(void); + STDMETHODIMP_(BOOL) IsDbContact(MCONTACT contactID); + STDMETHODIMP_(LONG) GetContactSize(void); + + STDMETHODIMP_(LONG) GetEventCount(MCONTACT contactID); + STDMETHODIMP_(MEVENT) AddEvent(MCONTACT contactID, DBEVENTINFO *dbe); + STDMETHODIMP_(BOOL) DeleteEvent(MCONTACT contactID, MEVENT hDbEvent); + STDMETHODIMP_(LONG) GetBlobSize(MEVENT hDbEvent); + STDMETHODIMP_(BOOL) GetEvent(MEVENT hDbEvent, DBEVENTINFO *dbe); + STDMETHODIMP_(BOOL) MarkEventRead(MCONTACT contactID, MEVENT hDbEvent); + STDMETHODIMP_(MCONTACT) GetEventContact(MEVENT hDbEvent); + STDMETHODIMP_(MEVENT) FindFirstEvent(MCONTACT contactID); + STDMETHODIMP_(MEVENT) FindFirstUnreadEvent(MCONTACT contactID); + STDMETHODIMP_(MEVENT) FindLastEvent(MCONTACT contactID); + STDMETHODIMP_(MEVENT) FindNextEvent(MCONTACT contactID, MEVENT hDbEvent); + STDMETHODIMP_(MEVENT) FindPrevEvent(MCONTACT contactID, MEVENT hDbEvent); + + STDMETHODIMP_(BOOL) EnumModuleNames(DBMODULEENUMPROC pFunc, void *pParam); + + STDMETHODIMP_(BOOL) GetContactSettingWorker(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv, int isStatic); + STDMETHODIMP_(BOOL) WriteContactSetting(MCONTACT contactID, DBCONTACTWRITESETTING *dbcws); + STDMETHODIMP_(BOOL) DeleteContactSetting(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting); + STDMETHODIMP_(BOOL) EnumContactSettings(MCONTACT hContact, DBSETTINGENUMPROC pfnEnumProc, const char *szModule, void *param); + + STDMETHODIMP_(BOOL) MetaMergeHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub); + STDMETHODIMP_(BOOL) MetaSplitHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub); + +protected: + STDMETHODIMP_(BOOL) Start(DBCHeckCallback *callback); + STDMETHODIMP_(BOOL) CheckDb(int phase, int firstTime); + STDMETHODIMP_(VOID) Destroy(); + +protected: + + void FillContacts(void); + + int Map(); + bool Remap(); + +protected: + TCHAR* m_tszProfileName; + bool m_safetyMode, m_bReadOnly, m_bShared, m_bEncrypted, m_bUsesPassword; + + //////////////////////////////////////////////////////////////////////////// + // database stuff +public: + MICryptoEngine *m_crypto; + +protected: + MDBX_env *m_env; + CMDBX_txn_ro m_txn; + + MDBX_dbi m_dbGlobal; + DBHeader m_header; + + HANDLE hSettingChangeEvent, hContactDeletedEvent, hContactAddedEvent, hEventMarkedRead; + + //////////////////////////////////////////////////////////////////////////// + // settings + + MDBX_dbi m_dbSettings; + MDBX_cursor *m_curSettings; + + HANDLE hService, hHook; + + //////////////////////////////////////////////////////////////////////////// + // contacts + + MDBX_dbi m_dbContacts; + MDBX_cursor *m_curContacts; + + uint32_t m_contactCount; + MCONTACT m_maxContactId; + + void GatherContactHistory(MCONTACT hContact, LIST &items); + + //////////////////////////////////////////////////////////////////////////// + // events + + MDBX_dbi m_dbEvents, m_dbEventsSort; + MDBX_cursor *m_curEvents, *m_curEventsSort; + MEVENT m_dwMaxEventId; + + HANDLE hEventAddedEvent, hEventDeletedEvent, hEventFilterAddedEvent; + + void FindNextUnread(const txn_ptr &_txn, DBCachedContact *cc, DBEventSortingKey &key2); + + //////////////////////////////////////////////////////////////////////////// + // modules + + MDBX_dbi m_dbModules; + MDBX_cursor *m_curModules; + + std::map m_Modules; + + int InitModules(); + + uint32_t GetModuleID(const char *szName); + char* GetModuleName(uint32_t dwId); + + DBCHeckCallback *cb; + + //////////////////////////////////////////////////////////////////////////// + // encryption + + MDBX_dbi m_dbCrypto; + + int InitCrypt(void); + CRYPTO_PROVIDER* SelectProvider(); + + void InitDialogs(); +}; \ No newline at end of file diff --git a/plugins/Dbx_mdbx/src/dbmodulechain.cpp b/plugins/Dbx_mdbx/src/dbmodulechain.cpp new file mode 100644 index 0000000000..b9b29b4c5a --- /dev/null +++ b/plugins/Dbx_mdbx/src/dbmodulechain.cpp @@ -0,0 +1,72 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (c) 2012-18 Miranda NG team (https://miranda-ng.org) +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" + +int CDbxMDBX::InitModules() +{ + txn_ptr_ro trnlck(m_txn); + cursor_ptr_ro cursor(m_curModules); + + MDBX_val key, data; + while (mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT) == MDBX_SUCCESS) { + uint32_t iMod = *(uint32_t*)key.iov_base; + const char *szMod = (const char*)data.iov_base; + m_Modules[iMod] = szMod; + } + return 0; +} + +// will create the offset if it needs to +uint32_t CDbxMDBX::GetModuleID(const char *szName) +{ + uint32_t iHash = mir_hashstr(szName); + if (m_Modules.find(iHash) == m_Modules.end()) { + MDBX_val key = { &iHash, sizeof(iHash) }, data = { (void*)szName, strlen(szName) + 1 }; + + for (;; Remap()) { + txn_ptr txn(m_env); + MDBX_CHECK(mdbx_put(txn, m_dbModules, &key, &data, 0), -1); + if (txn.commit() == MDBX_SUCCESS) + break; + } + m_Modules[iHash] = szName; + } + + return iHash; +} + +char* CDbxMDBX::GetModuleName(uint32_t dwId) +{ + auto it = m_Modules.find(dwId); + return it != m_Modules.end() ? const_cast(it->second.c_str()) : nullptr; +} + +STDMETHODIMP_(BOOL) CDbxMDBX::EnumModuleNames(DBMODULEENUMPROC pFunc, void *pParam) +{ + for (auto it = m_Modules.begin(); it != m_Modules.end(); ++it) + if (int ret = pFunc(it->second.c_str(), pParam)) + return ret; + + return 0; +} diff --git a/plugins/Dbx_mdbx/src/dbsettings.cpp b/plugins/Dbx_mdbx/src/dbsettings.cpp new file mode 100644 index 0000000000..eacf9d82c4 --- /dev/null +++ b/plugins/Dbx_mdbx/src/dbsettings.cpp @@ -0,0 +1,398 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (c) 2012-18 Miranda NG team (https://miranda-ng.org) +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" + +#define VLT(n) ((n == DBVT_UTF8 || n == DBVT_ENCRYPTED)?DBVT_ASCIIZ:n) + +static bool ValidLookupName(LPCSTR szModule, LPCSTR szSetting) +{ + if (!strcmp(szModule, META_PROTO)) + return strcmp(szSetting, "IsSubcontact") && strcmp(szSetting, "ParentMetaID"); + + if (!strcmp(szModule, "Ignore")) + return false; + + return true; +} + +int CDbxMDBX::GetContactSettingWorker(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv, int isStatic) +{ + if (szSetting == NULL || szModule == NULL) + return 1; + + size_t settingNameLen = strlen(szSetting); + size_t moduleNameLen = strlen(szModule); + +LBL_Seek: + char *szCachedSettingName = m_cache->GetCachedSetting(szModule, szSetting, moduleNameLen, settingNameLen); + + DBVARIANT *pCachedValue = m_cache->GetCachedValuePtr(contactID, szCachedSettingName, 0); + if (pCachedValue != NULL) { + if (pCachedValue->type == DBVT_ASCIIZ || pCachedValue->type == DBVT_UTF8) { + int cbOrigLen = dbv->cchVal; + char *cbOrigPtr = dbv->pszVal; + memcpy(dbv, pCachedValue, sizeof(DBVARIANT)); + if (isStatic) { + int cbLen = 0; + if (pCachedValue->pszVal != NULL) + cbLen = (int)strlen(pCachedValue->pszVal); + + cbOrigLen--; + dbv->pszVal = cbOrigPtr; + if (cbLen < cbOrigLen) + cbOrigLen = cbLen; + memcpy(dbv->pszVal, pCachedValue->pszVal, cbOrigLen); + dbv->pszVal[cbOrigLen] = 0; + dbv->cchVal = cbLen; + } + else { + dbv->pszVal = (char*)mir_alloc(strlen(pCachedValue->pszVal) + 1); + strcpy(dbv->pszVal, pCachedValue->pszVal); + } + } + else memcpy(dbv, pCachedValue, sizeof(DBVARIANT)); + + return (pCachedValue->type == DBVT_DELETED) ? 1 : 0; + } + + // never look db for the resident variable + if (szCachedSettingName[-1] != 0) + return 1; + + DBCachedContact *cc = (contactID) ? m_cache->GetCachedContact(contactID) : NULL; + + txn_ptr_ro trnlck(m_txn); + + DBSettingKey *keyVal = (DBSettingKey *)_alloca(sizeof(DBSettingKey) + settingNameLen); + keyVal->hContact = contactID; + keyVal->dwModuleId = GetModuleID(szModule); + memcpy(&keyVal->szSettingName, szSetting, settingNameLen + 1); + + + MDBX_val key = { keyVal, sizeof(DBSettingKey) + settingNameLen }, data; + if (mdbx_get(trnlck, m_dbSettings, &key, &data) != MDBX_SUCCESS) { + // try to get the missing mc setting from the active sub + if (cc && cc->IsMeta() && ValidLookupName(szModule, szSetting)) { + if (contactID = db_mc_getDefault(contactID)) { + if (szModule = GetContactProto(contactID)) { + moduleNameLen = strlen(szModule); + goto LBL_Seek; + } + } + } + return 1; + } + + const BYTE *pBlob = (const BYTE*)data.iov_base; + if (isStatic && (pBlob[0] & DBVTF_VARIABLELENGTH) && VLT(dbv->type) != VLT(pBlob[0])) + return 1; + + int varLen; + BYTE iType = dbv->type = pBlob[0]; pBlob++; + switch (iType) { + case DBVT_DELETED: /* this setting is deleted */ + dbv->type = DBVT_DELETED; + return 2; + + case DBVT_BYTE: dbv->bVal = *pBlob; break; + case DBVT_WORD: dbv->wVal = *(WORD*)pBlob; break; + case DBVT_DWORD: dbv->dVal = *(DWORD*)pBlob; break; + + case DBVT_UTF8: + case DBVT_ASCIIZ: + varLen = *(WORD*)pBlob; + pBlob += 2; + if (isStatic) { + dbv->cchVal--; + if (varLen < dbv->cchVal) + dbv->cchVal = varLen; + memcpy(dbv->pszVal, pBlob, dbv->cchVal); // decode + dbv->pszVal[dbv->cchVal] = 0; + dbv->cchVal = varLen; + } + else { + dbv->pszVal = (char*)mir_alloc(1 + varLen); + memcpy(dbv->pszVal, pBlob, varLen); + dbv->pszVal[varLen] = 0; + } + break; + + case DBVT_BLOB: + varLen = *(WORD*)pBlob; + pBlob += 2; + if (isStatic) { + if (varLen < dbv->cpbVal) + dbv->cpbVal = varLen; + memcpy(dbv->pbVal, pBlob, dbv->cpbVal); + } + else { + dbv->pbVal = (BYTE *)mir_alloc(varLen); + memcpy(dbv->pbVal, pBlob, varLen); + } + dbv->cpbVal = varLen; + break; + + case DBVT_ENCRYPTED: + if (m_crypto == NULL) + return 1; + + varLen = *(WORD*)pBlob; + pBlob += 2; + + size_t realLen; + ptrA decoded(m_crypto->decodeString(pBlob, varLen, &realLen)); + if (decoded == NULL) + return 1; + + varLen = (WORD)realLen; + dbv->type = DBVT_UTF8; + if (isStatic) { + dbv->cchVal--; + if (varLen < dbv->cchVal) + dbv->cchVal = varLen; + memcpy(dbv->pszVal, decoded, dbv->cchVal); + dbv->pszVal[dbv->cchVal] = 0; + dbv->cchVal = varLen; + } + else { + dbv->pszVal = (char*)mir_alloc(1 + varLen); + memcpy(dbv->pszVal, decoded, varLen); + dbv->pszVal[varLen] = 0; + } + break; + } + + /**** add to cache **********************/ + if (iType != DBVT_BLOB && iType != DBVT_ENCRYPTED) { + pCachedValue = m_cache->GetCachedValuePtr(contactID, szCachedSettingName, 1); + if (pCachedValue != NULL) + m_cache->SetCachedVariant(dbv, pCachedValue); + } + + return 0; +} + +STDMETHODIMP_(BOOL) CDbxMDBX::WriteContactSetting(MCONTACT contactID, DBCONTACTWRITESETTING *dbcws) +{ + if (dbcws == NULL || dbcws->szSetting == NULL || dbcws->szModule == NULL || m_bReadOnly) + return 1; + + // the db format can't tolerate more than 255 bytes of space (incl. null) for settings+module name + size_t settingNameLen = strlen(dbcws->szSetting); + size_t moduleNameLen = strlen(dbcws->szModule); + + // used for notifications + DBCONTACTWRITESETTING dbcwNotif = *dbcws; + if (dbcwNotif.value.type == DBVT_WCHAR) { + if (dbcwNotif.value.pszVal != NULL) { + T2Utf val(dbcwNotif.value.pwszVal); + if (val == NULL) + return 1; + + dbcwNotif.value.pszVal = NEWSTR_ALLOCA(val); + dbcwNotif.value.type = DBVT_UTF8; + } + else return 1; + } + + if (dbcwNotif.szModule == NULL || dbcwNotif.szSetting == NULL) + return 1; + + DBCONTACTWRITESETTING dbcwWork = dbcwNotif; + + mir_ptr pEncoded(NULL); + bool bIsEncrypted = false; + switch (dbcwWork.value.type) { + case DBVT_BYTE: case DBVT_WORD: case DBVT_DWORD: + break; + + case DBVT_ASCIIZ: case DBVT_UTF8: + bIsEncrypted = m_bEncrypted || IsSettingEncrypted(dbcws->szModule, dbcws->szSetting); +LBL_WriteString: + if (dbcwWork.value.pszVal == NULL) + return 1; + dbcwWork.value.cchVal = (WORD)strlen(dbcwWork.value.pszVal); + if (bIsEncrypted) { + size_t len; + BYTE *pResult = m_crypto->encodeString(dbcwWork.value.pszVal, &len); + if (pResult != NULL) { + pEncoded = dbcwWork.value.pbVal = pResult; + dbcwWork.value.cpbVal = (WORD)len; + dbcwWork.value.type = DBVT_ENCRYPTED; + } + } + break; + + case DBVT_UNENCRYPTED: + dbcwNotif.value.type = dbcwWork.value.type = DBVT_UTF8; + goto LBL_WriteString; + + case DBVT_BLOB: case DBVT_ENCRYPTED: + if (dbcwWork.value.pbVal == NULL) + return 1; + break; + default: + return 1; + } + + char *szCachedSettingName = m_cache->GetCachedSetting(dbcwWork.szModule, dbcwWork.szSetting, moduleNameLen, settingNameLen); + + // we don't cache blobs and passwords + if (dbcwWork.value.type != DBVT_BLOB && dbcwWork.value.type != DBVT_ENCRYPTED && !bIsEncrypted) { + DBVARIANT *pCachedValue = m_cache->GetCachedValuePtr(contactID, szCachedSettingName, 1); + if (pCachedValue != NULL) { + bool bIsIdentical = false; + if (pCachedValue->type == dbcwWork.value.type) { + switch (dbcwWork.value.type) { + case DBVT_BYTE: bIsIdentical = pCachedValue->bVal == dbcwWork.value.bVal; break; + case DBVT_WORD: bIsIdentical = pCachedValue->wVal == dbcwWork.value.wVal; break; + case DBVT_DWORD: bIsIdentical = pCachedValue->dVal == dbcwWork.value.dVal; break; + case DBVT_UTF8: + case DBVT_ASCIIZ: bIsIdentical = strcmp(pCachedValue->pszVal, dbcwWork.value.pszVal) == 0; break; + } + if (bIsIdentical) + return 0; + } + m_cache->SetCachedVariant(&dbcwWork.value, pCachedValue); + } + if (szCachedSettingName[-1] != 0) { + NotifyEventHooks(hSettingChangeEvent, contactID, (LPARAM)&dbcwWork); + return 0; + } + } + else m_cache->GetCachedValuePtr(contactID, szCachedSettingName, -1); + + DBSettingKey *keyVal = (DBSettingKey *)_alloca(sizeof(DBSettingKey) + settingNameLen); + keyVal->hContact = contactID; + keyVal->dwModuleId = GetModuleID(dbcws->szModule); + memcpy(&keyVal->szSettingName, dbcws->szSetting, settingNameLen + 1); + + + MDBX_val key = { keyVal, sizeof(DBSettingKey) + settingNameLen }, data; + + switch (dbcwWork.value.type) { + case DBVT_BYTE: data.iov_len = 2; break; + case DBVT_WORD: data.iov_len = 3; break; + case DBVT_DWORD: data.iov_len = 5; break; + + case DBVT_ASCIIZ: + case DBVT_UTF8: + data.iov_len = 3 + dbcwWork.value.cchVal; break; + + case DBVT_BLOB: + case DBVT_ENCRYPTED: + data.iov_len = 3 + dbcwWork.value.cpbVal; break; + } + + for (;; Remap()) { + txn_ptr trnlck(m_env); + MDBX_CHECK(mdbx_put(trnlck, m_dbSettings, &key, &data, MDBX_RESERVE), 1); + + BYTE *pBlob = (BYTE*)data.iov_base; + *pBlob++ = dbcwWork.value.type; + switch (dbcwWork.value.type) { + case DBVT_BYTE: *pBlob = dbcwWork.value.bVal; break; + case DBVT_WORD: *(WORD*)pBlob = dbcwWork.value.wVal; break; + case DBVT_DWORD: *(DWORD*)pBlob = dbcwWork.value.dVal; break; + + case DBVT_ASCIIZ: + case DBVT_UTF8: + data.iov_len = *(WORD*)pBlob = dbcwWork.value.cchVal; + pBlob += 2; + memcpy(pBlob, dbcwWork.value.pszVal, dbcwWork.value.cchVal); + break; + + case DBVT_BLOB: + case DBVT_ENCRYPTED: + data.iov_len = *(WORD*)pBlob = dbcwWork.value.cpbVal; + pBlob += 2; + memcpy(pBlob, dbcwWork.value.pbVal, dbcwWork.value.cpbVal); + } + + if (trnlck.commit() == MDBX_SUCCESS) + break; + } + + // notify + NotifyEventHooks(hSettingChangeEvent, contactID, (LPARAM)&dbcwNotif); + return 0; +} + +STDMETHODIMP_(BOOL) CDbxMDBX::DeleteContactSetting(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting) +{ + if (!szModule || !szSetting) + return 1; + + size_t settingNameLen = strlen(szSetting); + size_t moduleNameLen = strlen(szModule); + + char *szCachedSettingName = m_cache->GetCachedSetting(szModule, szSetting, moduleNameLen, settingNameLen); + + if (szCachedSettingName[-1] == 0) // it's not a resident variable + { + DBSettingKey *keyVal = (DBSettingKey*)_alloca(sizeof(DBSettingKey) + settingNameLen); + keyVal->hContact = contactID; + keyVal->dwModuleId = GetModuleID(szModule); + memcpy(&keyVal->szSettingName, szSetting, settingNameLen + 1); + + MDBX_val key = { keyVal, sizeof(DBSettingKey) + settingNameLen }; + + for (;; Remap()) { + txn_ptr trnlck(m_env); + MDBX_CHECK(mdbx_del(trnlck, m_dbSettings, &key, nullptr), 1); + if (trnlck.commit() == MDBX_SUCCESS) + break; + } + } + + m_cache->GetCachedValuePtr(contactID, szCachedSettingName, -1); + + // notify + DBCONTACTWRITESETTING dbcws = { 0 }; + dbcws.szModule = szModule; + dbcws.szSetting = szSetting; + dbcws.value.type = DBVT_DELETED; + NotifyEventHooks(hSettingChangeEvent, contactID, (LPARAM)&dbcws); + return 0; +} + +STDMETHODIMP_(BOOL) CDbxMDBX::EnumContactSettings(MCONTACT hContact, DBSETTINGENUMPROC pfnEnumProc, const char *szModule, void *param) +{ + int result = -1; + + DBSettingKey keyVal = { hContact, GetModuleID(szModule), 0 }; + txn_ptr_ro txn(m_txn); + cursor_ptr_ro cursor(m_curSettings); + + MDBX_val key = { &keyVal, sizeof(keyVal) }, data; + + for (int res = mdbx_cursor_get(cursor, &key, &data, MDBX_SET_RANGE); res == MDBX_SUCCESS; res = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) { + const DBSettingKey *pKey = (const DBSettingKey*)key.iov_base; + if (pKey->hContact != hContact || pKey->dwModuleId != keyVal.dwModuleId) + break; + result = pfnEnumProc(pKey->szSettingName, param); + } + + return result; +} diff --git a/plugins/Dbx_mdbx/src/dbutils.cpp b/plugins/Dbx_mdbx/src/dbutils.cpp new file mode 100644 index 0000000000..147b43ef30 --- /dev/null +++ b/plugins/Dbx_mdbx/src/dbutils.cpp @@ -0,0 +1,47 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (c) 2012-18 Miranda NG team (https://miranda-ng.org) +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" + +#define CMP_UINT(x, y) { if ((x) != (y)) return (x) < (y) ? -1 : 1; } + +int DBEventSortingKey::Compare(const MDBX_val *ax, const MDBX_val *bx) +{ + const DBEventSortingKey *a = (DBEventSortingKey *)ax->iov_base; + const DBEventSortingKey *b = (DBEventSortingKey *)bx->iov_base; + + CMP_UINT(a->hContact, b->hContact); + CMP_UINT(a->ts, b->ts); + CMP_UINT(a->hEvent, b->hEvent); + return 0; +} + +int DBSettingKey::Compare(const MDBX_val *ax, const MDBX_val *bx) +{ + const DBSettingKey *a = (DBSettingKey *)ax->iov_base; + const DBSettingKey *b = (DBSettingKey *)bx->iov_base; + + CMP_UINT(a->hContact, b->hContact); + CMP_UINT(a->dwModuleId, b->dwModuleId); + return strcmp(a->szSettingName, b->szSettingName); +} diff --git a/plugins/Dbx_mdbx/src/init.cpp b/plugins/Dbx_mdbx/src/init.cpp new file mode 100644 index 0000000000..5ab23c14f8 --- /dev/null +++ b/plugins/Dbx_mdbx/src/init.cpp @@ -0,0 +1,133 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (c) 2012-18 Miranda NG team (https://miranda-ng.org) +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" + +int hLangpack; + +static PLUGININFOEX pluginInfo = +{ + sizeof(PLUGININFOEX), + __PLUGIN_NAME, + PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), + __DESCRIPTION, + __AUTHOR, + __COPYRIGHT, + __AUTHORWEB, + UNICODE_AWARE | STATIC_PLUGIN, + // {7C3D0A33-2646-4001-9107-F35EA299D292} + { 0x7c3d0a33, 0x2646, 0x4001, { 0x91, 0x7, 0xf3, 0x5e, 0xa2, 0x99, 0xd2, 0x92 } } +}; + +HINSTANCE g_hInst = NULL; + +LIST g_Dbs(1, HandleKeySortT); + +///////////////////////////////////////////////////////////////////////////////////////// + +// returns 0 if the profile is created, EMKPRF* +static int makeDatabase(const TCHAR *profile) +{ + std::unique_ptr db(new CDbxMDBX(profile, 0)); + return db->Create(); +} + +// returns 0 if the given profile has a valid header +static int grokHeader(const TCHAR *profile) +{ + std::unique_ptr db(new CDbxMDBX(profile, DBMODE_SHARED | DBMODE_READONLY)); + return db->Check(); +} + +// returns 0 if all the APIs are injected otherwise, 1 +static MIDatabase* LoadDatabase(const TCHAR *profile, BOOL bReadOnly) +{ + // set the memory, lists & UTF8 manager + mir_getLP(&pluginInfo); + + std::unique_ptr db(new CDbxMDBX(profile, (bReadOnly) ? DBMODE_READONLY : 0)); + if (db->Load(false) != ERROR_SUCCESS) + return NULL; + + g_Dbs.insert(db.get()); + return db.release(); +} + +static int UnloadDatabase(MIDatabase *db) +{ + g_Dbs.remove((CDbxMDBX*)db); + delete (CDbxMDBX*)db; + return 0; +} + +MIDatabaseChecker* CheckDb(const TCHAR *profile, int *error) +{ + std::unique_ptr db(new CDbxMDBX(profile, DBMODE_READONLY)); + if (db->Load(true) != ERROR_SUCCESS) { + *error = ERROR_ACCESS_DENIED; + return NULL; + } + + if (db->PrepareCheck(error)) + return NULL; + + return db.release(); +} + +static DATABASELINK dblink = +{ + sizeof(DATABASELINK), + "dbx_mdbx", + L"MDBX database driver", + makeDatabase, + grokHeader, + LoadDatabase, + UnloadDatabase, + CheckDb +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD) +{ + return &pluginInfo; +} + +extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_DATABASE, MIID_LAST }; + +extern "C" __declspec(dllexport) int Load(void) +{ + RegisterDatabasePlugin(&dblink); + return 0; +} + +extern "C" __declspec(dllexport) int Unload(void) +{ + return 0; +} + +BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD, LPVOID) +{ + g_hInst = hInstDLL; + return TRUE; +} diff --git a/plugins/Dbx_mdbx/src/libmdbx b/plugins/Dbx_mdbx/src/libmdbx new file mode 160000 index 0000000000..18432ebfab --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx @@ -0,0 +1 @@ +Subproject commit 18432ebfab158ad2ae68a8a993040952f814dd95 diff --git a/plugins/Dbx_mdbx/src/resource.h b/plugins/Dbx_mdbx/src/resource.h new file mode 100644 index 0000000000..564189306d --- /dev/null +++ b/plugins/Dbx_mdbx/src/resource.h @@ -0,0 +1,35 @@ +//{{NO_DEPENDENCIES}} +// Включаемый файл, созданный в Microsoft Visual C++. +// Используется d:\Others\SVN\MirandaNG\trunk\plugins\Dbx_mdb\res\dbx_mdbx.rc +// +#define IDREMOVE 3 +#define IDI_ICONPASS 100 +#define IDI_LOGO 101 +#define IDD_LOGIN 102 +#define IDD_NEWPASS 103 +#define IDD_CHANGEPASS 104 +#define IDD_OPTIONS 105 +#define IDD_SELECT_CRYPTOPROVIDER 106 +#define IDC_HEADERBAR 1001 +#define IDC_LANG 1002 +#define IDC_USERPASS 1003 +#define IDC_USERPASS1 1004 +#define IDC_USERPASS2 1005 +#define IDC_OLDPASS 1006 +#define IDC_STANDARD 1007 +#define IDC_TOTAL 1008 +#define IDC_SELECTCRYPT_COMBO 1010 +#define IDC_CRYPTOPROVIDER_DESCR 1011 +#define IDC_CHECK1 1012 +#define IDC_CHECK_TOTALCRYPT 1012 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 107 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1013 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/plugins/Dbx_mdbx/src/stdafx.cxx b/plugins/Dbx_mdbx/src/stdafx.cxx new file mode 100644 index 0000000000..e579779bcc --- /dev/null +++ b/plugins/Dbx_mdbx/src/stdafx.cxx @@ -0,0 +1,18 @@ +/* +Copyright (C) 2012-18 Miranda NG team (https://miranda-ng.org) + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "stdafx.h" \ No newline at end of file diff --git a/plugins/Dbx_mdbx/src/stdafx.h b/plugins/Dbx_mdbx/src/stdafx.h new file mode 100644 index 0000000000..25e91a4001 --- /dev/null +++ b/plugins/Dbx_mdbx/src/stdafx.h @@ -0,0 +1,169 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (c) 2012-18 Miranda NG team (https://miranda-ng.org) +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libmdbx/mdbx.h" + +#ifndef thread_local +# define thread_local __declspec(thread) +#endif + + +class txn_ptr +{ + MDBX_txn *m_txn; +public: + __forceinline txn_ptr(MDBX_env *pEnv) + { + mdbx_txn_begin(pEnv, NULL, 0, &m_txn); + } + + __forceinline ~txn_ptr() + { + if (m_txn) + mdbx_txn_abort(m_txn); + } + + __forceinline operator MDBX_txn*() const { return m_txn; } + + __forceinline int commit() + { + MDBX_txn *tmp = m_txn; + m_txn = nullptr; + return mdbx_txn_commit(tmp); + } + + __forceinline void abort() + { + mdbx_txn_abort(m_txn); + m_txn = NULL; + } +}; + +struct CMDBX_txn_ro +{ + MDBX_txn *m_txn; + bool bIsActive; + mir_cs cs; + + __forceinline CMDBX_txn_ro() : m_txn(nullptr), bIsActive(false) {} + + __forceinline operator MDBX_txn* () { return m_txn; } + __forceinline MDBX_txn** operator &() { return &m_txn; } +}; + +class txn_ptr_ro +{ + CMDBX_txn_ro &m_txn; + bool bNeedReset; + mir_cslock lock; +public: + __forceinline txn_ptr_ro(CMDBX_txn_ro &txn) : m_txn(txn), bNeedReset(!txn.bIsActive), lock(m_txn.cs) + { + if (bNeedReset) + { + mdbx_txn_renew(m_txn); + m_txn.bIsActive = true; + } + } + __forceinline ~txn_ptr_ro() + { + if (bNeedReset) + { + mdbx_txn_reset(m_txn); + m_txn.bIsActive = false; + } + } + __forceinline operator MDBX_txn*() const { return m_txn; } +}; + +class cursor_ptr +{ + MDBX_cursor *m_cursor; + +public: + __forceinline cursor_ptr(MDBX_txn *_txn, MDBX_dbi _dbi) + { + if (mdbx_cursor_open(_txn, _dbi, &m_cursor) != MDBX_SUCCESS) + m_cursor = NULL; + } + + __forceinline ~cursor_ptr() + { + if (m_cursor) + mdbx_cursor_close(m_cursor); + } + + __forceinline operator MDBX_cursor*() const { return m_cursor; } +}; + +class cursor_ptr_ro +{ + MDBX_cursor *m_cursor; +public: + __forceinline cursor_ptr_ro(MDBX_cursor *cursor) : m_cursor(cursor) + { + mdbx_cursor_renew(mdbx_cursor_txn(m_cursor), m_cursor); + } + __forceinline operator MDBX_cursor*() const { return m_cursor; } +}; + +#define MDBX_CHECK(A,B) \ + switch (A) { \ + case MDBX_SUCCESS: break; \ + case MDBX_MAP_FULL: continue; \ + default: return (B); } + + + + +#include "dbintf.h" +#include "resource.h" +#include "version.h" + +extern HINSTANCE g_hInst; +extern LIST g_Dbs; + +#include "ui.h" diff --git a/plugins/Dbx_mdbx/src/ui.cpp b/plugins/Dbx_mdbx/src/ui.cpp new file mode 100644 index 0000000000..6cf2f6bee8 --- /dev/null +++ b/plugins/Dbx_mdbx/src/ui.cpp @@ -0,0 +1,202 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (c) 2012-18 Miranda NG team (https://miranda-ng.org) +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" + +static HGENMENU hSetPwdMenu; + +static UINT oldLangID; +void LanguageChanged(HWND hwndDlg) +{ + UINT_PTR LangID = (UINT_PTR)GetKeyboardLayout(0); + char Lang[3] = { 0 }; + if (LangID != oldLangID) { + oldLangID = LangID; + GetLocaleInfoA(MAKELCID((LangID & 0xffffffff), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, Lang, 2); + Lang[0] = toupper(Lang[0]); + Lang[1] = tolower(Lang[1]); + SetDlgItemTextA(hwndDlg, IDC_LANG, Lang); + } +} + + +///////////////////////////////////////////////////////////////////////////////////////// + +static bool CheckOldPassword(HWND hwndDlg, CDbxMDBX *db) +{ + if (db->usesPassword()) + { + TCHAR buf[100]; + GetDlgItemText(hwndDlg, IDC_OLDPASS, buf, _countof(buf)); + pass_ptrA oldPass(mir_utf8encodeW(buf)); + if (!db->m_crypto->checkPassword(oldPass)) + { + SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Wrong old password entered!")); + return false; + } + } + return true; +} + +static INT_PTR CALLBACK sttChangePassword(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + DlgChangePassParam *param = (DlgChangePassParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + TCHAR buf[100]; + + switch (uMsg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + SendDlgItemMessage(hwndDlg, IDC_HEADERBAR, WM_SETICON, ICON_SMALL, (LPARAM)IcoLib_GetIconByHandle(iconList[0].hIcolib, true)); + + param = (DlgChangePassParam*)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + + oldLangID = 0; + SetTimer(hwndDlg, 1, 200, NULL); + LanguageChanged(hwndDlg); + return TRUE; + + case WM_CTLCOLORSTATIC: + if ((HWND)lParam == GetDlgItem(hwndDlg, IDC_LANG)) { + SetTextColor((HDC)wParam, GetSysColor(COLOR_HIGHLIGHTTEXT)); + SetBkMode((HDC)wParam, TRANSPARENT); + return (INT_PTR)GetSysColorBrush(COLOR_HIGHLIGHT); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + + case IDREMOVE: + if (!CheckOldPassword(hwndDlg, param->db)) { + LBL_Error: + SendDlgItemMessage(hwndDlg, IDC_HEADERBAR, WM_NCPAINT, 0, 0); + SetDlgItemTextA(hwndDlg, IDC_USERPASS1, ""); + SetDlgItemTextA(hwndDlg, IDC_USERPASS2, ""); + } + else { + // param->db->WriteSignature(dbSignatureU); + param->db->SetPassword(nullptr); + param->db->StoreKey(); + EndDialog(hwndDlg, IDREMOVE); + } + break; + + case IDOK: + TCHAR buf2[100]; + GetDlgItemText(hwndDlg, IDC_USERPASS1, buf2, _countof(buf2)); + if (wcslen(buf2) < 3) { + SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Password is too short!")); + goto LBL_Error; + } + + GetDlgItemText(hwndDlg, IDC_USERPASS2, buf, _countof(buf)); + if (wcscmp(buf2, buf)) { + SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Passwords do not match!")); + goto LBL_Error; + } + + if (!CheckOldPassword(hwndDlg, param->db)) + goto LBL_Error; + + // param->db->WriteSignature(dbSignatureE); + param->db->SetPassword(buf2); + param->db->StoreKey(); + SecureZeroMemory(buf2, sizeof(buf2)); + EndDialog(hwndDlg, IDOK); + } + break; + + case WM_TIMER: + LanguageChanged(hwndDlg); + return FALSE; + + case WM_DESTROY: + KillTimer(hwndDlg, 1); + IcoLib_ReleaseIcon((HICON)SendMessage(hwndDlg, WM_GETICON, ICON_SMALL, 0)); + } + + return FALSE; +} + +static INT_PTR ChangePassword(void* obj, WPARAM, LPARAM) +{ + CDbxMDBX *db = (CDbxMDBX*)obj; + DlgChangePassParam param = { db }; + DialogBoxParam(g_hInst, MAKEINTRESOURCE(db->usesPassword() ? IDD_CHANGEPASS : IDD_NEWPASS), 0, sttChangePassword, (LPARAM)¶m); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + + +static int OnOptionsInit(PVOID obj, WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { sizeof(odp) }; + odp.position = -790000000; + odp.flags = ODPF_BOLDGROUPS; + odp.szTitle.a = LPGEN("Database"); + odp.pDialog = new COptionsDialog((CDbxMDBX*)obj); + Options_AddPage(wParam, &odp); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDbxMDBX::UpdateMenuItem() +{ + Menu_ModifyItem(hSetPwdMenu, _A2T(GetMenuTitle()), iconList[1].hIcolib); +} + +static int OnModulesLoaded(PVOID obj, WPARAM, LPARAM) +{ + CDbxMDBX *db = (CDbxMDBX*)obj; + + Icon_Register(g_hInst, LPGEN("Database"), iconList, _countof(iconList), "mdbx"); + + HookEventObj(ME_OPT_INITIALISE, OnOptionsInit, db); + + CMenuItem mi; + + // main menu item + mi.root = Menu_CreateRoot(MO_MAIN, LPGENW("Database"), 500000000, iconList[0].hIcolib); + Menu_ConfigureItem(mi.root, MCI_OPT_UID, "F7C5567C-D1EE-484B-B4F6-24677A5AAAEF"); + + SET_UID(mi, 0x50321866, 0xba1, 0x46dd, 0xb3, 0xa6, 0xc3, 0xcc, 0x55, 0xf2, 0x42, 0x9e); + mi.hIcolibItem = iconList[1].hIcolib; + mi.name.a = db->GetMenuTitle(); + mi.pszService = MS_DB_CHANGEPASSWORD; + hSetPwdMenu = Menu_AddMainMenuItem(&mi); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDbxMDBX::InitDialogs() +{ + hService = CreateServiceFunctionObj(MS_DB_CHANGEPASSWORD, ChangePassword, this); + hHook = HookEventObj(ME_SYSTEM_MODULESLOADED, OnModulesLoaded, this); +} diff --git a/plugins/Dbx_mdbx/src/ui.h b/plugins/Dbx_mdbx/src/ui.h new file mode 100644 index 0000000000..704c380cfa --- /dev/null +++ b/plugins/Dbx_mdbx/src/ui.h @@ -0,0 +1,187 @@ +static IconItem iconList[] = +{ + { LPGEN("Logo"), "logo", IDI_LOGO }, + { LPGEN("Password"), "password", IDI_ICONPASS } +}; + +#define MS_DB_CHANGEPASSWORD "DB/UI/ChangePassword" + +class COptionsDialog : public CDlgBase +{ + CCtrlCheck m_chkStandart; + CCtrlCheck m_chkTotal; + CCtrlButton m_btnChangePass; + CDbxMDBX *m_db; + + void OnInitDialog() + { + m_chkStandart.SetState(!m_db->isEncrypted()); + m_chkTotal.SetState(m_db->isEncrypted()); + m_btnChangePass.SetTextA(Translate(m_db->GetMenuTitle())); + } + + void OnApply() + { + m_db->EnableEncryption(m_chkTotal.GetState() != 0); + m_chkStandart.SetState(!m_db->isEncrypted()); + m_chkTotal.SetState(m_db->isEncrypted()); + } + + void ChangePass(CCtrlButton*) + { + CallService(MS_DB_CHANGEPASSWORD, 0, 0); + } + +public: + COptionsDialog(CDbxMDBX *db) : + CDlgBase(g_hInst, IDD_OPTIONS), + m_chkStandart(this, IDC_STANDARD), + m_chkTotal(this, IDC_TOTAL), + m_btnChangePass(this, IDC_USERPASS), + m_db(db) + { + m_btnChangePass.OnClick = Callback(this, &COptionsDialog::ChangePass); + } +}; + +class CSelectCryptoDialog : public CDlgBase +{ + CCtrlCombo m_combo; + CCtrlData m_descr; + CCtrlCheck m_chkTotalCrypt; + CRYPTO_PROVIDER **m_provs; + size_t m_provscount; + CRYPTO_PROVIDER *m_selected; + bool m_bTotalEncryption; + + void OnInitDialog() + { + for (size_t i = 0; i < m_provscount; i++) + { + CRYPTO_PROVIDER *prov = m_provs[i]; + m_combo.AddStringA(prov->pszName, i); + } + m_combo.SetCurSel(0); + m_descr.SetText(m_provs[0]->ptszDescr); + } + + void OnClose() + { + m_selected = m_provs[ m_combo.GetItemData(m_combo.GetCurSel()) ]; + m_bTotalEncryption = m_chkTotalCrypt.GetState() != 0; + } + + void OnComboChanged(CCtrlCombo*) + { + m_descr.SetText(m_provs[m_combo.GetItemData(m_combo.GetCurSel())]->ptszDescr); + } + +public: + CSelectCryptoDialog(CRYPTO_PROVIDER **provs, size_t count) : + CDlgBase(g_hInst, IDD_SELECT_CRYPTOPROVIDER), + m_combo(this, IDC_SELECTCRYPT_COMBO), + m_descr(this, IDC_CRYPTOPROVIDER_DESCR), + m_chkTotalCrypt(this, IDC_CHECK_TOTALCRYPT), + m_provs(provs), + m_provscount(count), + m_selected(nullptr) + { + m_combo.OnChange = Callback(this, &CSelectCryptoDialog::OnComboChanged); + } + + inline CRYPTO_PROVIDER* GetSelected() + { + return m_selected; + } + inline bool TotalSelected() + { + return m_bTotalEncryption; + } +}; + +struct DlgChangePassParam +{ + CDbxMDBX *db; + TCHAR newPass[100]; + unsigned short wrongPass; +}; + +class CEnterPasswordDialog : public CDlgBase +{ + CCtrlData m_header; + CCtrlData m_language; + CCtrlEdit m_passwordEdit; + CCtrlButton m_buttonOK; + + DlgChangePassParam *m_param; + + INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) + { + if (msg == WM_TIMER) + { + UINT_PTR LangID = (UINT_PTR)GetKeyboardLayout(0); + char Lang[3] = { 0 }; + GetLocaleInfoA(MAKELCID((LangID & 0xffffffff), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, Lang, 2); + Lang[0] = toupper(Lang[0]); + Lang[1] = tolower(Lang[1]); + m_language.SetTextA(Lang); + return FALSE; + } + else if (msg == WM_CTLCOLORSTATIC) + { + if ((HWND)lParam == m_language.GetHwnd()) { + SetTextColor((HDC)wParam, GetSysColor(COLOR_HIGHLIGHTTEXT)); + SetBkMode((HDC)wParam, TRANSPARENT); + return (INT_PTR)GetSysColorBrush(COLOR_HIGHLIGHT); + } + } + return CDlgBase::DlgProc(msg, wParam, lParam); + } + + void OnInitDialog() + { + m_header.SendMsg(WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon(g_hInst, MAKEINTRESOURCE(iconList[0].defIconID))); + if (m_param->wrongPass) + { + if (m_param->wrongPass > 2) + { + m_passwordEdit.Disable(); + m_buttonOK.Disable(); + m_header.SetText(TranslateT("Too many errors!")); + } + else + { + m_header.SetText(TranslateT("Password is not correct!")); + } + } + else + { + m_header.SetText(TranslateT("Please type in your password")); + } + SetTimer(m_hwnd, 1, 200, NULL); + } + + void OnOK(CCtrlButton*) + { + m_passwordEdit.GetText(m_param->newPass, _countof(m_param->newPass)); + EndDialog(m_hwnd, -128); + } + + void OnDestroy() + { + KillTimer(m_hwnd, 1); + } + +public: + CEnterPasswordDialog(DlgChangePassParam *param) : + CDlgBase(g_hInst, IDD_LOGIN), + m_header(this, IDC_HEADERBAR), + m_language(this, IDC_LANG), + m_passwordEdit(this, IDC_USERPASS), + m_buttonOK(this, IDOK), + m_param(param) + { + m_buttonOK.OnClick = Callback(this, &CEnterPasswordDialog::OnOK); + } + +}; diff --git a/plugins/Dbx_mdbx/src/version.h b/plugins/Dbx_mdbx/src/version.h new file mode 100644 index 0000000000..0b97e64886 --- /dev/null +++ b/plugins/Dbx_mdbx/src/version.h @@ -0,0 +1,13 @@ +#define __MAJOR_VERSION 0 +#define __MINOR_VERSION 95 +#define __RELEASE_NUM 8 +#define __BUILD_NUM 1 + +#include + +#define __PLUGIN_NAME "Miranda NG MDBX database driver (https://github.com/leo-yuriev/libmdbx)" +#define __FILENAME "dbx_mdbx.dll" +#define __DESCRIPTION "Provides Miranda database support: global settings, contacts, history, settings per contact." +#define __AUTHOR "Miranda-NG project" +#define __AUTHORWEB "https://miranda-ng.org/p/dbx_mdbx/" +#define __COPYRIGHT "© 2015-18 Miranda NG team" -- cgit v1.2.3