123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- /*
- * Copyright 2005 - 2016 Zarafa and its licensors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- #include <kopano/platform.h>
- #include <kopano/ECGuid.h>
- #include <kopano/ECInterfaceDefs.h>
- #include "ics.h"
- #include "pcutil.hpp"
- #include "ECABContainer.h"
- #include "IECImportAddressbookChanges.h"
- #include "ECMsgStore.h"
- #include "ECExportAddressbookChanges.h"
- #include <kopano/ECLogger.h>
- #include <ECSyncLog.h>
- #include <kopano/stringutil.h>
- #include <kopano/Util.h>
- #include <edkmdb.h>
- ECExportAddressbookChanges::ECExportAddressbookChanges(ECMsgStore *lpStore) :
- m_lpMsgStore(lpStore)
- {
- ECSyncLog::GetLogger(&m_lpLogger);
- }
- ECExportAddressbookChanges::~ECExportAddressbookChanges() {
- MAPIFreeBuffer(m_lpRawChanges);
- MAPIFreeBuffer(m_lpChanges);
- if(m_lpImporter)
- m_lpImporter->Release();
- if (m_lpLogger)
- m_lpLogger->Release();
- }
- HRESULT ECExportAddressbookChanges::QueryInterface(REFIID refiid, void **lppInterface) {
- REGISTER_INTERFACE2(ECUnknown, this);
- REGISTER_INTERFACE2(IECExportAddressbookChanges, &this->m_xECExportAddressbookChanges);
- REGISTER_INTERFACE2(IUnknown, &this->m_xECExportAddressbookChanges);
- return MAPI_E_INTERFACE_NOT_SUPPORTED;
- }
- HRESULT ECExportAddressbookChanges::Config(LPSTREAM lpStream, ULONG ulFlags, IECImportAddressbookChanges *lpCollector)
- {
- HRESULT hr;
- LARGE_INTEGER lint = {{ 0, 0 }};
- ABEID abeid;
- STATSTG sStatStg;
- ULONG ulCount = 0;
- ULONG ulProcessed = 0;
- ULONG ulRead = 0;
- ICSCHANGE *lpLastChange = NULL;
- int n = 0;
- // Read state from stream
- hr = lpStream->Stat(&sStatStg, 0);
- if(hr != hrSuccess)
- return hr;
- hr = lpStream->Seek(lint, STREAM_SEEK_SET, NULL);
- if (hr != hrSuccess)
- return hr;
- if(sStatStg.cbSize.QuadPart < 8) {
- m_ulChangeId = 0;
- m_setProcessed.clear();
- } else {
- m_setProcessed.clear();
- hr = lpStream->Read(&m_ulChangeId, sizeof(ULONG), &ulRead);
- if(hr != hrSuccess)
- return hr;
- hr = lpStream->Read(&ulCount, sizeof(ULONG), &ulRead);
- if(hr != hrSuccess)
- return hr;
- while(ulCount) {
- hr = lpStream->Read(&ulProcessed, sizeof(ULONG), &ulRead);
- if(hr != hrSuccess)
- return hr;
- m_setProcessed.insert(ulProcessed);
- --ulCount;
- }
- }
- // ulFlags ignored
- m_lpImporter = lpCollector;
- m_lpImporter->AddRef();
- memset(&abeid, 0, sizeof(ABEID));
-
- abeid.ulType = MAPI_ABCONT;
- memcpy(&abeid.guid, &MUIDECSAB, sizeof(GUID));
- abeid.ulId = 1; // 1 is the first container
-
- // The parent source key is the entryid of the AB container that we're sync'ing
- MAPIFreeBuffer(m_lpChanges);
- m_lpChanges = NULL;
- MAPIFreeBuffer(m_lpRawChanges);
- m_lpRawChanges = NULL;
- hr = m_lpMsgStore->lpTransport->HrGetChanges(std::string((char *)&abeid, sizeof(ABEID)), 0, m_ulChangeId, ICS_SYNC_AB, 0, NULL, &m_ulMaxChangeId, &m_ulChanges, &m_lpRawChanges);
- if(hr != hrSuccess)
- return hr;
- ZLOG_DEBUG(m_lpLogger, "Got %u address book changes from server.", m_ulChanges);
- if (m_ulChanges > 0) {
- /*
- * Sort the changes:
- * Users must exist before the company, but before the group they are member of
- * Groups must exist after the company and the users which are the members
- * Companies must exist after the users and groups, so create that last
- *
- * MSw: The above comment seems to make this impossible, but the way things are ordered are:
- * 1. Users
- * 2. Groups
- * 3. Companies
- *
- * We also want to group changes for each unique object together so we can easily
- * perform some filtering on them:
- * - Add + Change = Add
- * - Change + Delete = Delete
- * - Add + (Change +) Delete = -
- * - Delete + Add = Delete + Add (eg. user changes from object class from/to contact)
- */
- std::stable_sort(m_lpRawChanges, m_lpRawChanges + m_ulChanges, LeftPrecedesRight);
- // Now go through the changes to remove multiple changes for the same object.
- if ((hr = MAPIAllocateBuffer(sizeof(ICSCHANGE) * m_ulChanges, (void **)&m_lpChanges)) != hrSuccess)
- return hr;
- for (ULONG i = 0; i < m_ulChanges; ++i) {
- // First check if this change hasn't been processed yet
- if (m_setProcessed.find(m_lpRawChanges[i].ulChangeId) != m_setProcessed.end())
- continue;
- // Now check if we changed objects. if so we write the last change to the changes list
- if (lpLastChange && CompareABEID(lpLastChange->sSourceKey.cb, (LPENTRYID)lpLastChange->sSourceKey.lpb,
- m_lpRawChanges[i].sSourceKey.cb, (LPENTRYID)m_lpRawChanges[i].sSourceKey.lpb) == false) {
- m_lpChanges[n++] = *lpLastChange;
- lpLastChange = NULL;
- }
- if (!lpLastChange)
- lpLastChange = &m_lpRawChanges[i];
- else {
- switch (m_lpRawChanges[i].ulChangeType) {
- case ICS_AB_NEW:
- // This shouldn't happen since apparently we have another change for the same object.
- if (lpLastChange->ulChangeType == ICS_AB_DELETE) {
- // user was deleted and re-created (probably as different object class), so keep both events
- ZLOG_DEBUG(m_lpLogger, "Got an ICS_AB_NEW change for an object that was deleted, modifying into CHANGE. sourcekey=%s", bin2hex(m_lpRawChanges[i].sSourceKey.cb, m_lpRawChanges[i].sSourceKey.lpb).c_str());
- lpLastChange->ulChangeType = ICS_AB_CHANGE;
- } else
- ZLOG_DEBUG(m_lpLogger, "Got an ICS_AB_NEW change for an object we've seen before. sourcekey=%s", bin2hex(m_lpRawChanges[i].sSourceKey.cb, m_lpRawChanges[i].sSourceKey.lpb).c_str());
- break;
- case ICS_AB_CHANGE:
- // The only valid previous change could have been an add, since the server doesn't track
- // multiple changes and we can't change an object that was just deleted.
- // We'll ignore this in any case.
- if (lpLastChange->ulChangeType != ICS_AB_NEW)
- ZLOG_DEBUG(m_lpLogger, "Got an ICS_AB_CHANGE with something else than a ICS_AB_NEW as the previous changes. prev_change=%04x, sourcekey=%s", lpLastChange->ulChangeType, bin2hex(m_lpRawChanges[i].sSourceKey.cb, m_lpRawChanges[i].sSourceKey.lpb).c_str());
- ZLOG_DEBUG(m_lpLogger, "Ignoring ICS_AB_CHANGE due to previous ICS_AB_NEW. sourcekey=%s", bin2hex(m_lpRawChanges[i].sSourceKey.cb, m_lpRawChanges[i].sSourceKey.lpb).c_str());
- break;
- case ICS_AB_DELETE:
- if (lpLastChange->ulChangeType == ICS_AB_NEW) {
- ZLOG_DEBUG(m_lpLogger, "Ignoring previous ICS_AB_NEW due to current ICS_AB_DELETE. sourcekey=%s", bin2hex(m_lpRawChanges[i].sSourceKey.cb, m_lpRawChanges[i].sSourceKey.lpb).c_str());
- lpLastChange = NULL; // An add and a delete results in nothing.
- }
- else if (lpLastChange->ulChangeType == ICS_AB_CHANGE) {
- // We'll ignore the previous change and write the delete now. This way we allow another object
- // with the same ID to be added after this object.
- ZLOG_DEBUG(m_lpLogger, "Replacing previous ICS_AB_CHANGE with current ICS_AB_DELETE. sourcekey=%s", bin2hex(m_lpRawChanges[i].sSourceKey.cb, m_lpRawChanges[i].sSourceKey.lpb).c_str());
- m_lpChanges[n++] = m_lpRawChanges[i];
- lpLastChange = NULL;
- }
- break;
- default:
- ZLOG_DEBUG(m_lpLogger, "Got an unknown change. change=%04x, sourcekey=%s", m_lpRawChanges[i].ulChangeType, bin2hex(m_lpRawChanges[i].sSourceKey.cb, m_lpRawChanges[i].sSourceKey.lpb).c_str());
- break;
- }
- }
- }
- if (lpLastChange) {
- m_lpChanges[n++] = *lpLastChange;
- lpLastChange = NULL;
- }
- }
- m_ulChanges = n;
- ZLOG_DEBUG(m_lpLogger, "Got %u address book changes after sorting/filtering.", m_ulChanges);
- // Next change is first one
- m_ulThisChange = 0;
- return hr;
- }
- HRESULT ECExportAddressbookChanges::Synchronize(ULONG *lpulSteps, ULONG *lpulProgress)
- {
- HRESULT hr = hrSuccess;
-
- // Check if we're already done
- if (m_ulThisChange >= m_ulChanges)
- return hrSuccess;
-
- if (m_lpChanges[m_ulThisChange].sSourceKey.cb < sizeof(ABEID))
- return MAPI_E_INVALID_PARAMETER;
- auto eid = reinterpret_cast<const ABEID *>(m_lpChanges[m_ulThisChange].sSourceKey.lpb);
- ZLOG_DEBUG(m_lpLogger, "abchange type=%04x, sourcekey=%s", m_lpChanges[m_ulThisChange].ulChangeType, bin2hex(m_lpChanges[m_ulThisChange].sSourceKey.cb, m_lpChanges[m_ulThisChange].sSourceKey.lpb).c_str());
- switch(m_lpChanges[m_ulThisChange].ulChangeType) {
- case ICS_AB_CHANGE:
- case ICS_AB_NEW: {
- hr = m_lpImporter->ImportABChange(eid->ulType, m_lpChanges[m_ulThisChange].sSourceKey.cb, (LPENTRYID)m_lpChanges[m_ulThisChange].sSourceKey.lpb);
- break;
- }
- case ICS_AB_DELETE: {
- hr = m_lpImporter->ImportABDeletion(eid->ulType, m_lpChanges[m_ulThisChange].sSourceKey.cb, (LPENTRYID)m_lpChanges[m_ulThisChange].sSourceKey.lpb);
- break;
- }
- default:
- return MAPI_E_INVALID_PARAMETER;
- }
-
- if (hr == SYNC_E_IGNORE)
- hr = hrSuccess;
- else if (hr == MAPI_E_INVALID_TYPE) {
- m_lpLogger->Log(EC_LOGLEVEL_WARNING, "Ignoring invalid entry, type=%04x, sourcekey=%s", m_lpChanges[m_ulThisChange].ulChangeType, bin2hex(m_lpChanges[m_ulThisChange].sSourceKey.cb, m_lpChanges[m_ulThisChange].sSourceKey.lpb).c_str());
- hr = hrSuccess;
- }
- else if (hr != hrSuccess) {
- ZLOG_DEBUG(m_lpLogger, "failed type=%04x, hr=%s, sourcekey=%s", m_lpChanges[m_ulThisChange].ulChangeType, stringify(hr, true).c_str(), bin2hex(m_lpChanges[m_ulThisChange].sSourceKey.cb, m_lpChanges[m_ulThisChange].sSourceKey.lpb).c_str());
- return hr;
- }
- // Mark the change as processed
- m_setProcessed.insert(m_lpChanges[m_ulThisChange].ulChangeId);
- ++m_ulThisChange;
- if(lpulSteps)
- *lpulSteps = m_ulChanges;
-
- if(lpulProgress)
- *lpulProgress = m_ulThisChange;
-
- if(m_ulThisChange >= m_ulChanges)
- hr = hrSuccess;
- else
- hr = SYNC_W_PROGRESS;
-
- return hr;
- }
- HRESULT ECExportAddressbookChanges::UpdateState(LPSTREAM lpStream)
- {
- HRESULT hr;
- LARGE_INTEGER zero = {{0,0}};
- ULARGE_INTEGER uzero = {{0,0}};
- ULONG ulCount = 0;
- ULONG ulWritten = 0;
- ULONG ulProcessed = 0;
- if(m_ulThisChange == m_ulChanges) {
- // All changes have been processed, we can discard processed changes and go to the next server change ID
- m_setProcessed.clear();
- // The last change ID we received is always the highest change ID
- if(m_ulMaxChangeId > 0)
- m_ulChangeId = m_ulMaxChangeId;
- }
- hr = lpStream->Seek(zero, STREAM_SEEK_SET, NULL);
- if(hr != hrSuccess)
- return hr;
- hr = lpStream->SetSize(uzero);
- if(hr != hrSuccess)
- return hr;
- // Write the change ID
- hr = lpStream->Write(&m_ulChangeId, sizeof(ULONG), &ulWritten);
- if(hr != hrSuccess)
- return hr;
- ulCount = m_setProcessed.size();
- // Write the number of processed IDs to follow
- hr = lpStream->Write(&ulCount, sizeof(ULONG), &ulWritten);
- if(hr != hrSuccess)
- return hr;
- // Write the processed IDs
- for (const auto &pc : m_setProcessed) {
- ulProcessed = pc;
- hr = lpStream->Write(&ulProcessed, sizeof(ULONG), &ulWritten);
- if(hr != hrSuccess)
- return hr;
- }
- lpStream->Seek(zero, STREAM_SEEK_SET, NULL);
- // All done
- return hrSuccess;
- }
- /**
- * Compares two ICSCHANGE objects and determines which of the two should precede the other.
- * This function is supposed to be used as the predicate in std::stable_sort.
- *
- * The rules for determining if a certain change should precede another are as follows:
- * - Users take precendence over Groups and Companies.
- * - Groups take precedence over Companies only.
- * - Companies never take precedence.
- * - If the changes are of the same type, precedence is determined by comparing the binary
- * values using SortCompareABEntryID. If the compare returns less than 0, the left change
- * should take precedence, otherwise it shouldn't. Note that that doesn't imply that the right
- * change takes precedence.
- *
- * For the ICS system it is only important to group the users, groups and companies. But for the
- * dupliacte change filter it is necessary to group changes for a single object as well. Hence the
- * 4th rule.
- *
- * @param[in] left
- * An ICSCHANGE structure who's sSourceKey is interpreted as an ABEID stucture. It is compared to
- * the right parameter.
- * @param[in] right
- * An ICSCHANGE structure who's sSourceKey is interpreted as an ABEID stucture. It is compared to
- * the left parameter.
- *
- * @return boolean
- * @retval true
- * The ICSCHANGE specified by left must be processed before the ICSCHANGE
- * specified bu right.
- * @retval false
- * The ICSCHANGE specified by left must NOT be processed before the ICSCHANGE
- * specified by right. Note that this does not mean that the ICSCHANGE specified
- * by right must be processed before the ICSCHANGE specified by left.
- */
- bool ECExportAddressbookChanges::LeftPrecedesRight(const ICSCHANGE &left, const ICSCHANGE &right)
- {
- ULONG ulTypeLeft = ((ABEID*)left.sSourceKey.lpb)->ulType;
- assert(ulTypeLeft == MAPI_MAILUSER || ulTypeLeft == MAPI_DISTLIST || ulTypeLeft == MAPI_ABCONT);
- ULONG ulTypeRight = ((ABEID*)right.sSourceKey.lpb)->ulType;
- assert(ulTypeRight == MAPI_MAILUSER || ulTypeRight == MAPI_DISTLIST || ulTypeRight == MAPI_ABCONT);
- if (ulTypeLeft == ulTypeRight)
- return SortCompareABEID(left.sSourceKey.cb, (LPENTRYID)left.sSourceKey.lpb, right.sSourceKey.cb, (LPENTRYID)right.sSourceKey.lpb) < 0;
- if (ulTypeRight == MAPI_ABCONT) // Company is always bigger.
- return true;
- if (ulTypeRight == MAPI_DISTLIST && ulTypeLeft == MAPI_MAILUSER) // Group is only bigger than user.
- return true;
- return false;
- }
- DEF_ULONGMETHOD1(TRACE_MAPI, ECExportAddressbookChanges, ECExportAddressbookChanges, AddRef, (void))
- DEF_ULONGMETHOD1(TRACE_MAPI, ECExportAddressbookChanges, ECExportAddressbookChanges, Release, (void))
- DEF_HRMETHOD1(TRACE_MAPI, ECExportAddressbookChanges, ECExportAddressbookChanges, QueryInterface, (REFIID, refiid), (void **, lppInterface))
- DEF_HRMETHOD1(TRACE_MAPI, ECExportAddressbookChanges, ECExportAddressbookChanges, Config, (LPSTREAM, lpStream), (ULONG, ulFlags), (IECImportAddressbookChanges *, lpCollector))
- DEF_HRMETHOD1(TRACE_MAPI, ECExportAddressbookChanges, ECExportAddressbookChanges, Synchronize, (ULONG *, lpulSteps), (ULONG *, lpulProgress))
- DEF_HRMETHOD1(TRACE_MAPI, ECExportAddressbookChanges, ECExportAddressbookChanges, UpdateState, (LPSTREAM, lpStream))
|