ArchiveControlImpl.cpp 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <kopano/platform.h>
  18. #include <memory>
  19. #include <new> // std::bad_alloc
  20. #include <list> // std::list
  21. #include "ArchiveControlImpl.h"
  22. #include "ECArchiverLogger.h"
  23. #include "ArchiverSession.h"
  24. #include "ArchiveStateCollector.h"
  25. #include "ArchiveStateUpdater.h"
  26. #include <kopano/userutil.h>
  27. #include <kopano/mapiext.h>
  28. #include "helpers/StoreHelper.h"
  29. #include "operations/copier.h"
  30. #include "operations/deleter.h"
  31. #include "operations/stubber.h"
  32. #include <kopano/ECConfig.h>
  33. #include "ECIterators.h"
  34. #include <kopano/ECRestriction.h>
  35. #include "HrException.h"
  36. #include "ArchiveManage.h"
  37. #include <kopano/MAPIErrors.h>
  38. #include <kopano/charset/convert.h>
  39. using namespace KC::helpers;
  40. using namespace KC::operations;
  41. namespace KC {
  42. /**
  43. * Create a new Archive object.
  44. *
  45. * @param[in] lpSession
  46. * Pointer to the Session.
  47. * @param[in] lpConfig
  48. * Pointer to an ECConfig object that determines the operational options.
  49. * @param[in] lpLogger
  50. * Pointer to an ECLogger object that's used for logging.
  51. * @param[in] bForceCleanup Force a cleanup operation to continue, even
  52. * if the settings aren't safe.
  53. * @param[out] lpptrArchiver
  54. * Pointer to a ArchivePtr that will be assigned the address of the returned object.
  55. */
  56. HRESULT ArchiveControlImpl::Create(ArchiverSessionPtr ptrSession, ECConfig *lpConfig, ECLogger *lpLogger, bool bForceCleanup, ArchiveControlPtr *lpptrArchiveControl)
  57. {
  58. std::unique_ptr<ArchiveControlImpl> ptrArchiveControl(
  59. new(std::nothrow) ArchiveControlImpl(ptrSession, lpConfig, lpLogger, bForceCleanup));
  60. if (ptrArchiveControl == nullptr)
  61. return MAPI_E_NOT_ENOUGH_MEMORY;
  62. HRESULT hr = ptrArchiveControl->Init();
  63. if (hr != hrSuccess)
  64. return hr;
  65. *lpptrArchiveControl = std::move(ptrArchiveControl);
  66. return hrSuccess;
  67. }
  68. /**
  69. * @param[in] lpSession
  70. * Pointer to the Session.
  71. * @param[in] lpConfig
  72. * Pointer to an ECConfig object that determines the operational options.
  73. * @param[in] lpLogger
  74. * Pointer to an ECLogger object that's used for logging.
  75. * @param[in] bForceCleanup Force a cleanup operation to continue, even
  76. * if the settings aren't safe.
  77. */
  78. ArchiveControlImpl::ArchiveControlImpl(ArchiverSessionPtr ptrSession, ECConfig *lpConfig, ECLogger *lpLogger, bool bForceCleanup)
  79. : m_ptrSession(ptrSession)
  80. , m_lpConfig(lpConfig)
  81. , m_lpLogger(new ECArchiverLogger(lpLogger))
  82. , m_cleanupAction(caStore)
  83. , m_bForceCleanup(bForceCleanup), __propmap(5)
  84. {
  85. }
  86. ArchiveControlImpl::~ArchiveControlImpl()
  87. {
  88. m_lpLogger->Release();
  89. }
  90. /**
  91. * Initialize the Archiver object.
  92. */
  93. HRESULT ArchiveControlImpl::Init()
  94. {
  95. m_bArchiveEnable = parseBool(m_lpConfig->GetSetting("archive_enable", "", "no"));
  96. m_ulArchiveAfter = atoi(m_lpConfig->GetSetting("archive_after", "", "30"));
  97. m_bDeleteEnable = parseBool(m_lpConfig->GetSetting("delete_enable", "", "no"));
  98. m_bDeleteUnread = parseBool(m_lpConfig->GetSetting("delete_unread", "", "no"));
  99. m_ulDeleteAfter = atoi(m_lpConfig->GetSetting("delete_after", "", "0"));
  100. m_bStubEnable = parseBool(m_lpConfig->GetSetting("stub_enable", "", "no"));
  101. m_bStubUnread = parseBool(m_lpConfig->GetSetting("stub_unread", "", "no"));
  102. m_ulStubAfter = atoi(m_lpConfig->GetSetting("stub_after", "", "0"));
  103. m_bPurgeEnable = parseBool(m_lpConfig->GetSetting("purge_enable", "", "no"));
  104. m_ulPurgeAfter = atoi(m_lpConfig->GetSetting("purge_after", "", "2555"));
  105. const char *lpszCleanupAction = m_lpConfig->GetSetting("cleanup_action");
  106. if (lpszCleanupAction == NULL || *lpszCleanupAction == '\0') {
  107. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Empty cleanup_action specified in config.");
  108. return MAPI_E_INVALID_PARAMETER;
  109. }
  110. if (strcasecmp(lpszCleanupAction, "delete") == 0)
  111. m_cleanupAction = caDelete;
  112. else if (strcasecmp(lpszCleanupAction, "store") == 0)
  113. m_cleanupAction = caStore;
  114. else if (strcasecmp(lpszCleanupAction, "none") == 0)
  115. m_cleanupAction = caNone;
  116. else {
  117. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Unknown cleanup_action specified in config: '%s'", lpszCleanupAction);
  118. return MAPI_E_INVALID_PARAMETER;
  119. }
  120. m_bCleanupFollowPurgeAfter = parseBool(m_lpConfig->GetSetting("cleanup_follow_purge_after", "", "no"));
  121. GetSystemTimeAsFileTime(&m_ftCurrent);
  122. return hrSuccess;
  123. }
  124. /**
  125. * Archive messages for all users. Optionaly only user that have their store on the server
  126. * to which the archiver is connected will have their messages archived.
  127. *
  128. * @param[in] bLocalOnly
  129. * If set to true only messsages for users that have their store on the local server
  130. * will be archived.
  131. */
  132. eResult ArchiveControlImpl::ArchiveAll(bool bLocalOnly, bool bAutoAttach, unsigned int ulFlags)
  133. {
  134. HRESULT hr = hrSuccess;
  135. if (ulFlags != ArchiveManage::Writable &&
  136. ulFlags != ArchiveManage::ReadOnly && ulFlags != 0)
  137. return MAPIErrorToArchiveError(MAPI_E_INVALID_PARAMETER);
  138. if (bAutoAttach || parseBool(m_lpConfig->GetSetting("enable_auto_attach"))) {
  139. ArchiveStateCollectorPtr ptrArchiveStateCollector;
  140. ArchiveStateUpdaterPtr ptrArchiveStateUpdater;
  141. hr = ArchiveStateCollector::Create(m_ptrSession, m_lpLogger, &ptrArchiveStateCollector);
  142. if (hr != hrSuccess)
  143. return MAPIErrorToArchiveError(hr);
  144. hr = ptrArchiveStateCollector->GetArchiveStateUpdater(&ptrArchiveStateUpdater);
  145. if (hr != hrSuccess)
  146. return MAPIErrorToArchiveError(hr);
  147. if (ulFlags == 0) {
  148. if (parseBool(m_lpConfig->GetSetting("auto_attach_writable")))
  149. ulFlags = ArchiveManage::Writable;
  150. else
  151. ulFlags = ArchiveManage::ReadOnly;
  152. }
  153. hr = ptrArchiveStateUpdater->UpdateAll(ulFlags);
  154. if (hr != hrSuccess)
  155. return MAPIErrorToArchiveError(hr);
  156. }
  157. return MAPIErrorToArchiveError(ProcessAll(bLocalOnly, &ArchiveControlImpl::DoArchive));
  158. }
  159. /**
  160. * Archive the messages of a particular user.
  161. *
  162. * @param[in] strUser
  163. * The username for which to archive the messages.
  164. */
  165. eResult ArchiveControlImpl::Archive(const tstring &strUser, bool bAutoAttach, unsigned int ulFlags)
  166. {
  167. HRESULT hr = hrSuccess;
  168. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive(): function entry.");
  169. ScopedUserLogging sul(m_lpLogger, strUser);
  170. if (ulFlags != ArchiveManage::Writable && ulFlags != ArchiveManage::ReadOnly && ulFlags != 0) {
  171. hr = MAPI_E_INVALID_PARAMETER;
  172. m_lpLogger->Log(EC_LOGLEVEL_INFO, "ArchiveControlImpl::Archive(): invalid parameter.");
  173. goto exit;
  174. }
  175. if (bAutoAttach || parseBool(m_lpConfig->GetSetting("enable_auto_attach"))) {
  176. ArchiveStateCollectorPtr ptrArchiveStateCollector;
  177. ArchiveStateUpdaterPtr ptrArchiveStateUpdater;
  178. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive(): about to create collector.");
  179. hr = ArchiveStateCollector::Create(m_ptrSession, m_lpLogger, &ptrArchiveStateCollector);
  180. if (hr != hrSuccess)
  181. goto exit;
  182. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive(): about to get updater.");
  183. hr = ptrArchiveStateCollector->GetArchiveStateUpdater(&ptrArchiveStateUpdater);
  184. if (hr != hrSuccess)
  185. goto exit;
  186. if (ulFlags == 0) {
  187. if (parseBool(m_lpConfig->GetSetting("auto_attach_writable")))
  188. ulFlags = ArchiveManage::Writable;
  189. else
  190. ulFlags = ArchiveManage::ReadOnly;
  191. }
  192. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive(): about to update store of user %ls. Flags: 0x%08X", strUser.c_str(), ulFlags);
  193. hr = ptrArchiveStateUpdater->Update(strUser, ulFlags);
  194. if (hr != hrSuccess)
  195. goto exit;
  196. }
  197. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive(): about to do real archive run.");
  198. hr = DoArchive(strUser);
  199. exit:
  200. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive() at exit. Return code before transformation: 0x%08x (%s).", hr, GetMAPIErrorMessage(hr));
  201. return MAPIErrorToArchiveError(hr);
  202. }
  203. /**
  204. * Cleanup the archive(s) of all users. Optionaly only user that have their store on the server
  205. * to which the archiver is connected will have their messages archived.
  206. *
  207. * @param[in] bLocalOnly
  208. * If set to true only messsages for users that have their store on the local server
  209. * will be archived.
  210. */
  211. eResult ArchiveControlImpl::CleanupAll(bool bLocalOnly)
  212. {
  213. HRESULT hr = hrSuccess;
  214. hr = CheckSafeCleanupSettings();
  215. if (hr == hrSuccess)
  216. hr = ProcessAll(bLocalOnly, &ArchiveControlImpl::DoCleanup);
  217. return MAPIErrorToArchiveError(hr);
  218. }
  219. /**
  220. * Cleanup the archive(s) of a particular user.
  221. * Cleaning up is currently defined as detecting which messages were deleted
  222. * from the primary store and moving the archives of those messages to the
  223. * special deleted folder.
  224. *
  225. * @param[in] strUser
  226. * The username for which to archive the messages.
  227. */
  228. eResult ArchiveControlImpl::Cleanup(const tstring &strUser)
  229. {
  230. HRESULT hr = hrSuccess;
  231. ScopedUserLogging sul(m_lpLogger, strUser);
  232. hr = CheckSafeCleanupSettings();
  233. if (hr == hrSuccess)
  234. hr = DoCleanup(strUser);
  235. return MAPIErrorToArchiveError(hr);
  236. }
  237. /**
  238. * Process all users.
  239. *
  240. * @param[in] bLocalOnly Limit to users that have a store on the local server.
  241. * @param[in] fnProcess The method to execute to do the actual processing.
  242. */
  243. HRESULT ArchiveControlImpl::ProcessAll(bool bLocalOnly, fnProcess_t fnProcess)
  244. {
  245. typedef std::list<tstring> StringList;
  246. HRESULT hr = hrSuccess;
  247. StringList lstUsers;
  248. UserList lstUserEntries;
  249. bool bHaveErrors = false;
  250. hr = GetArchivedUserList(m_ptrSession->GetMAPISession(),
  251. m_ptrSession->GetSSLPath(), m_ptrSession->GetSSLPass(),
  252. &lstUsers, bLocalOnly);
  253. if (hr != hrSuccess) {
  254. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to obtain user list. (hr=0x%08x)", hr);
  255. goto exit;
  256. }
  257. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Processing %zu%s users.", lstUsers.size(), (bLocalOnly ? " local" : ""));
  258. for (const auto &user : lstUsers) {
  259. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Processing user '" TSTRING_PRINTF "'.", user.c_str());
  260. HRESULT hrTmp = (this->*fnProcess)(user);
  261. if (FAILED(hrTmp)) {
  262. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to process user '" TSTRING_PRINTF "'. (hr=0x%08x)", user.c_str(), hrTmp);
  263. bHaveErrors = true;
  264. } else if (hrTmp == MAPI_W_PARTIAL_COMPLETION) {
  265. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Errors occurred while processing user '" TSTRING_PRINTF "'.", user.c_str());
  266. bHaveErrors = true;
  267. }
  268. }
  269. exit:
  270. if (hr == hrSuccess && bHaveErrors)
  271. hr = MAPI_W_PARTIAL_COMPLETION;
  272. return hr;
  273. }
  274. /**
  275. * Get the name of a folder from a IMAPIFolder object
  276. *
  277. * @param[in] folder Pointer to IMAPIFolder object to be queried
  278. * @return tstring containing folder name
  279. */
  280. tstring
  281. ArchiveControlImpl::getfoldername(LPMAPIFOLDER folder)
  282. {
  283. SPropValuePtr foldername;
  284. if (HrGetOneProp(folder, PR_DISPLAY_NAME, &~foldername) != hrSuccess)
  285. return convert_to<tstring>("<Unnamed>");
  286. return tstring(foldername->Value.LPSZ);
  287. }
  288. /**
  289. * Remove soft-deleted items from a IMAPIFolder object
  290. *
  291. * @param[in] folder Pointer to IMAPIFolder object to be queried
  292. * @param[in] strUser String constaining name of store
  293. * @return HRESULT. 0 on succes, error code on failure
  294. */
  295. HRESULT
  296. ArchiveControlImpl::purgesoftdeleteditems(LPMAPIFOLDER folder, const tstring& strUser)
  297. {
  298. HRESULT hr = hrSuccess;
  299. MAPITablePtr table;
  300. if ((hr = folder->GetContentsTable(SHOW_SOFT_DELETES, &~table)) != hrSuccess) {
  301. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get search folder contents table. (hr=%s)", stringify(hr, true).c_str());
  302. return hr;
  303. }
  304. static constexpr const SizedSPropTagArray(1, props) = {1, {PR_ENTRYID}};
  305. if ((hr = table->SetColumns(props, 0)) != hrSuccess) {
  306. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to set columns on table. (hr=%s)", stringify(hr, true).c_str());
  307. return hr;
  308. }
  309. unsigned int found = 0;
  310. unsigned int totalfound = 0;
  311. do {
  312. SRowSetPtr rowSet;
  313. if ((hr = table->QueryRows(100, 0, &rowSet)) != hrSuccess) {
  314. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get rows from table. (hr=%s)", stringify(hr, true).c_str());
  315. continue;
  316. }
  317. found = rowSet.size();
  318. totalfound += found;
  319. EntryListPtr ptrEntryList;
  320. hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~ptrEntryList);
  321. if (hr != hrSuccess)
  322. continue;
  323. hr = MAPIAllocateMore(sizeof(SBinary), ptrEntryList, (LPVOID*)&ptrEntryList->lpbin);
  324. if (hr != hrSuccess)
  325. continue;
  326. ptrEntryList->cValues = 1;
  327. for (unsigned int i = 0; i < found; ++i) {
  328. ptrEntryList->lpbin[0].cb = rowSet->aRow[i].lpProps[0].Value.bin.cb;
  329. ptrEntryList->lpbin[0].lpb = rowSet->aRow[i].lpProps[0].Value.bin.lpb;
  330. if ((hr = folder->DeleteMessages(ptrEntryList, 0, NULL, DELETE_HARD_DELETE)) != hrSuccess)
  331. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to delete message. (hr=%s)", stringify(hr, true).c_str());
  332. }
  333. } while (found);
  334. if (totalfound)
  335. m_lpLogger->Log(
  336. EC_LOGLEVEL_INFO,
  337. "Store %ls: %u soft-deleted messages removed from folder %ls",
  338. strUser.c_str(),
  339. totalfound,
  340. getfoldername(folder).c_str()
  341. );
  342. return hr;
  343. }
  344. /**
  345. * Remove soft-deleted items of a user store
  346. *
  347. * @param[in] strUser tstring containing user name
  348. * @return HRESULT. 0 on succes, error code on failure
  349. */
  350. HRESULT
  351. ArchiveControlImpl::purgesoftdeletedmessages(const tstring& strUser)
  352. {
  353. MsgStorePtr store;
  354. HRESULT hr = m_ptrSession->OpenStoreByName(strUser, &~store);
  355. if (hr != hrSuccess) {
  356. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to open user store. (hr=%s)", stringify(hr, true).c_str());
  357. return hr;
  358. }
  359. SPropValuePtr ptrPropValue;
  360. hr = HrGetOneProp(store, PR_IPM_SUBTREE_ENTRYID, &~ptrPropValue);
  361. if (hr != hrSuccess) {
  362. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get PR_IPM_SUBTREE_ENTRYID. (hr=%s)", stringify(hr, true).c_str());
  363. return hr;
  364. }
  365. MAPIFolderPtr ipmSubtree;
  366. ULONG type = 0;
  367. if ((hr = store->OpenEntry(ptrPropValue->Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrPropValue->Value.bin.lpb), nullptr, MAPI_BEST_ACCESS | fMapiDeferredErrors, &type, &~ipmSubtree)) != hrSuccess) {
  368. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to open ipmSubtree. (hr=%s)", stringify(hr, true).c_str());
  369. return hr;
  370. }
  371. ECFolderIterator iEnd;
  372. for (auto i = ECFolderIterator(ipmSubtree, fMapiDeferredErrors, 0); i != iEnd; ++i)
  373. hr = purgesoftdeleteditems(*i, strUser);
  374. return hr;
  375. }
  376. /**
  377. * Perform the actual archive operation for a specific user.
  378. *
  379. * @param[in] strUser tstring containing user name
  380. */
  381. HRESULT ArchiveControlImpl::DoArchive(const tstring& strUser)
  382. {
  383. HRESULT hr = hrSuccess;
  384. MsgStorePtr ptrUserStore;
  385. StoreHelperPtr ptrStoreHelper;
  386. MAPIFolderPtr ptrSearchArchiveFolder;
  387. MAPIFolderPtr ptrSearchDeleteFolder;
  388. MAPIFolderPtr ptrSearchStubFolder;
  389. ObjectEntryList lstArchives;
  390. bool bHaveErrors = false;
  391. CopierPtr ptrCopyOp;
  392. DeleterPtr ptrDeleteOp;
  393. StubberPtr ptrStubOp;
  394. if (strUser.empty()) {
  395. hr = MAPI_E_INVALID_PARAMETER;
  396. goto exitpm;
  397. }
  398. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Archiving store for user '" TSTRING_PRINTF "'", strUser.c_str());
  399. hr = m_ptrSession->OpenStoreByName(strUser, &~ptrUserStore);
  400. if (hr != hrSuccess) {
  401. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to open store. (hr=%s)", stringify(hr, true).c_str());
  402. goto exitpm;
  403. }
  404. PROPMAP_INIT_NAMED_ID(ARCHIVE_STORE_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidStoreEntryIds)
  405. PROPMAP_INIT_NAMED_ID(ARCHIVE_ITEM_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidItemEntryIds)
  406. PROPMAP_INIT_NAMED_ID(ORIGINAL_SOURCEKEY, PT_BINARY, PSETID_Archive, dispidOrigSourceKey)
  407. PROPMAP_INIT_NAMED_ID(STUBBED, PT_BOOLEAN, PSETID_Archive, dispidStubbed)
  408. PROPMAP_INIT_NAMED_ID(DIRTY, PT_BOOLEAN, PSETID_Archive, dispidDirty)
  409. PROPMAP_INIT(ptrUserStore)
  410. hr = StoreHelper::Create(ptrUserStore, &ptrStoreHelper);
  411. if (hr != hrSuccess) {
  412. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to create store helper. (hr=%s)", stringify(hr, true).c_str());
  413. goto exitpm;
  414. }
  415. hr = ptrStoreHelper->GetArchiveList(&lstArchives);
  416. if (hr != hrSuccess) {
  417. if (hr == MAPI_E_CORRUPT_DATA) {
  418. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "List of archives is corrupt for user '" TSTRING_PRINTF "', skipping user.", strUser.c_str());
  419. hr = hrSuccess;
  420. } else
  421. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get list of archives. (hr=%s)", stringify(hr, true).c_str());
  422. goto exitpm;
  423. }
  424. if (lstArchives.empty()) {
  425. m_lpLogger->Log(EC_LOGLEVEL_INFO, "'" TSTRING_PRINTF "' has no attached archives", strUser.c_str());
  426. goto exitpm;
  427. }
  428. hr = ptrStoreHelper->GetSearchFolders(&~ptrSearchArchiveFolder, &~ptrSearchDeleteFolder, &~ptrSearchStubFolder);
  429. if (hr != hrSuccess) {
  430. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get the search folders. (hr=%s)", stringify(hr, true).c_str());
  431. goto exitpm;
  432. }
  433. // Create and hook the three dependent steps
  434. if (m_bArchiveEnable && m_ulArchiveAfter >= 0) {
  435. SizedSPropTagArray(5, sptaExcludeProps) = {5, {PROP_ARCHIVE_STORE_ENTRYIDS, PROP_ARCHIVE_ITEM_ENTRYIDS, PROP_STUBBED, PROP_DIRTY, PROP_ORIGINAL_SOURCEKEY}};
  436. ptrCopyOp.reset(new Copier(m_ptrSession, m_lpConfig, m_lpLogger,
  437. lstArchives, sptaExcludeProps, m_ulArchiveAfter, true));
  438. }
  439. if (m_bDeleteEnable && m_ulDeleteAfter >= 0) {
  440. ptrDeleteOp.reset(new Deleter(m_lpLogger, m_ulDeleteAfter, m_bDeleteUnread));
  441. if (ptrCopyOp)
  442. ptrCopyOp->SetDeleteOperation(ptrDeleteOp);
  443. }
  444. if (m_bStubEnable && m_ulStubAfter >= 0) {
  445. ptrStubOp.reset(new Stubber(m_lpLogger, PROP_STUBBED, m_ulStubAfter, m_bStubUnread));
  446. if (ptrCopyOp)
  447. ptrCopyOp->SetStubOperation(ptrStubOp);
  448. }
  449. // Now execute them
  450. if (ptrCopyOp) {
  451. // Archive all unarchived messages that are old enough
  452. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Archiving messages");
  453. hr = ProcessFolder(ptrSearchArchiveFolder, ptrCopyOp);
  454. if (FAILED(hr)) {
  455. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to archive messages. (hr=%s)", stringify(hr, true).c_str());
  456. goto exitpm;
  457. } else if (hr == MAPI_W_PARTIAL_COMPLETION) {
  458. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Some message could not be archived");
  459. bHaveErrors = true;
  460. hr = hrSuccess;
  461. }
  462. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Done archiving messages");
  463. }
  464. if (ptrDeleteOp) {
  465. // First delete all messages that are eligible for deletion, so we do not unneccesary stub them first
  466. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Deleting old messages");
  467. hr = ProcessFolder(ptrSearchDeleteFolder, ptrDeleteOp);
  468. if (FAILED(hr)) {
  469. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to delete old messages. (hr=%s)", stringify(hr, true).c_str());
  470. goto exitpm;
  471. } else if (hr == MAPI_W_PARTIAL_COMPLETION) {
  472. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Some message could not be deleted");
  473. bHaveErrors = true;
  474. hr = hrSuccess;
  475. }
  476. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Done deleting messages");
  477. }
  478. if (ptrStubOp) {
  479. // Now stub the remaining messages (if they are old enough)
  480. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Stubbing messages");
  481. hr = ProcessFolder(ptrSearchStubFolder, ptrStubOp);
  482. if (FAILED(hr)) {
  483. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to stub messages. (hr=%s)", stringify(hr, true).c_str());
  484. goto exitpm;
  485. } else if (hr == MAPI_W_PARTIAL_COMPLETION) {
  486. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Some message could not be stubbed");
  487. bHaveErrors = true;
  488. hr = hrSuccess;
  489. }
  490. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Done stubbing messages");
  491. }
  492. if (m_bPurgeEnable) {
  493. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Purging archive(s)");
  494. hr = PurgeArchives(lstArchives);
  495. if (FAILED(hr)) {
  496. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to purge archive(s). (hr=%s)", stringify(hr, true).c_str());
  497. goto exitpm;
  498. } else if (hr == MAPI_W_PARTIAL_COMPLETION) {
  499. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Some archives could not be purged");
  500. bHaveErrors = true;
  501. hr = hrSuccess;
  502. }
  503. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Done purging archive(s)");
  504. }
  505. exitpm:
  506. if (hr == hrSuccess && bHaveErrors)
  507. hr = MAPI_W_PARTIAL_COMPLETION;
  508. return hr;
  509. }
  510. /**
  511. * Perform the actual cleanup operation for a specific user.
  512. *
  513. * @param[in] strUser tstring containing user name
  514. */
  515. HRESULT ArchiveControlImpl::DoCleanup(const tstring &strUser)
  516. {
  517. HRESULT hr;
  518. MsgStorePtr ptrUserStore;
  519. StoreHelperPtr ptrStoreHelper;
  520. ObjectEntryList lstArchives;
  521. SRestrictionPtr ptrRestriction;
  522. if (strUser.empty())
  523. return MAPI_E_INVALID_PARAMETER;
  524. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Cleanup store for user '" TSTRING_PRINTF "', mode=%s", strUser.c_str(), m_lpConfig->GetSetting("cleanup_action"));
  525. if (m_bCleanupFollowPurgeAfter) {
  526. ULARGE_INTEGER li;
  527. SPropValue sPropRefTime;
  528. li.LowPart = m_ftCurrent.dwLowDateTime;
  529. li.HighPart = m_ftCurrent.dwHighDateTime;
  530. li.QuadPart -= (m_ulPurgeAfter * _DAY);
  531. sPropRefTime.ulPropTag = PROP_TAG(PT_SYSTIME, 0);
  532. sPropRefTime.Value.ft.dwLowDateTime = li.LowPart;
  533. sPropRefTime.Value.ft.dwHighDateTime = li.HighPart;
  534. hr = ECOrRestriction(
  535. ECAndRestriction(
  536. ECExistRestriction(PR_MESSAGE_DELIVERY_TIME) +
  537. ECPropertyRestriction(RELOP_LT, PR_MESSAGE_DELIVERY_TIME, &sPropRefTime, ECRestriction::Cheap)
  538. ) +
  539. ECAndRestriction(
  540. ECExistRestriction(PR_CLIENT_SUBMIT_TIME) +
  541. ECPropertyRestriction(RELOP_LT, PR_CLIENT_SUBMIT_TIME, &sPropRefTime, ECRestriction::Cheap)
  542. )
  543. ).CreateMAPIRestriction(&~ptrRestriction, 0);
  544. if (hr != hrSuccess)
  545. return hr;
  546. }
  547. hr = m_ptrSession->OpenStoreByName(strUser, &~ptrUserStore);
  548. if (hr != hrSuccess) {
  549. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to open store. (hr=0x%08x)", hr);
  550. return hr;
  551. }
  552. hr = StoreHelper::Create(ptrUserStore, &ptrStoreHelper);
  553. if (hr != hrSuccess) {
  554. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to create store helper. (hr=0x%08x)", hr);
  555. return hr;
  556. }
  557. hr = ptrStoreHelper->GetArchiveList(&lstArchives);
  558. if (hr != hrSuccess) {
  559. if (hr == MAPI_E_CORRUPT_DATA) {
  560. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "List of archives is corrupt for user '" TSTRING_PRINTF "', skipping user.", strUser.c_str());
  561. hr = hrSuccess;
  562. } else
  563. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get list of archives. (hr=0x%08x)", hr);
  564. return hr;
  565. }
  566. if (lstArchives.empty()) {
  567. m_lpLogger->Log(EC_LOGLEVEL_INFO, "'" TSTRING_PRINTF "' has no attached archives", strUser.c_str());
  568. return hr;
  569. }
  570. for (const auto &arc : lstArchives) {
  571. HRESULT hrTmp = hrSuccess;
  572. hrTmp = CleanupArchive(arc, ptrUserStore, ptrRestriction);
  573. if (hrTmp != hrSuccess)
  574. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to cleanup archive. (hr=0x%08x)", hr);
  575. }
  576. return hrSuccess;
  577. }
  578. /**
  579. * Process a search folder and place an additional restriction on it to get the messages
  580. * that should really be archived.
  581. *
  582. * @param[in] ptrFolder
  583. * A MAPIFolderPtr that points to the search folder to be processed.
  584. * @param[in] lpArchiveOperation
  585. * The pointer to a IArchiveOperation derived object that's used to perform
  586. * the actual processing.
  587. * @param[in] ulAge
  588. * The age in days since the message was delivered, that a message must be before
  589. * it will be processed.
  590. * @param[in] bProcessUnread
  591. * If set to true, unread messages will also be processed. Otherwise unread message
  592. * will be left untouched.
  593. */
  594. HRESULT ArchiveControlImpl::ProcessFolder(MAPIFolderPtr &ptrFolder, ArchiveOperationPtr ptrArchiveOperation)
  595. {
  596. HRESULT hr = hrSuccess;
  597. MAPITablePtr ptrTable;
  598. SRestrictionPtr ptrRestriction;
  599. SSortOrderSetPtr ptrSortOrder;
  600. SRowSetPtr ptrRowSet;
  601. MessagePtr ptrMessage;
  602. bool bHaveErrors = false;
  603. const tstring strFolderRestore = m_lpLogger->GetFolder();
  604. static constexpr const SizedSPropTagArray(3, sptaProps) =
  605. {3, {PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID}};
  606. hr = ptrFolder->GetContentsTable(fMapiDeferredErrors, &~ptrTable);
  607. if (hr != hrSuccess) {
  608. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get search folder contents table. (hr=%s)", stringify(hr, true).c_str());
  609. goto exit;
  610. }
  611. hr = ptrTable->SetColumns(sptaProps, TBL_BATCH);
  612. if (hr != hrSuccess) {
  613. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to set columns on table. (hr=%s)", stringify(hr, true).c_str());
  614. goto exit;
  615. }
  616. hr = ptrArchiveOperation->GetRestriction(ptrFolder, &~ptrRestriction);
  617. if (hr != hrSuccess) {
  618. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get restriction from operation. (hr=%s)", stringify(hr, true).c_str());
  619. goto exit;
  620. }
  621. hr = ptrTable->Restrict(ptrRestriction, TBL_BATCH);
  622. if (hr != hrSuccess) {
  623. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to set restriction on table. (hr=%s)", stringify(hr, true).c_str());
  624. goto exit;
  625. }
  626. hr = MAPIAllocateBuffer(CbNewSSortOrderSet(1), &~ptrSortOrder);
  627. if (hr != hrSuccess)
  628. goto exit;
  629. ptrSortOrder->cSorts = 1;
  630. ptrSortOrder->cCategories = 0;
  631. ptrSortOrder->cExpanded = 0;
  632. ptrSortOrder->aSort[0].ulPropTag = PR_PARENT_ENTRYID;
  633. ptrSortOrder->aSort[0].ulOrder = TABLE_SORT_ASCEND ;
  634. hr = ptrTable->SortTable(ptrSortOrder, TBL_BATCH);
  635. if (hr != hrSuccess) {
  636. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to sort table. (hr=%s)", stringify(hr, true).c_str());
  637. goto exit;
  638. }
  639. do {
  640. hr = ptrTable->QueryRows(50, 0, &ptrRowSet);
  641. if (hr != hrSuccess) {
  642. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get rows from table. (hr=%s)", stringify(hr, true).c_str());
  643. goto exit;
  644. }
  645. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Processing batch of %u messages", ptrRowSet.size());
  646. for (ULONG i = 0; i < ptrRowSet.size(); ++i) {
  647. hr = ptrArchiveOperation->ProcessEntry(ptrFolder, ptrRowSet[i].cValues, ptrRowSet[i].lpProps);
  648. if (hr != hrSuccess) {
  649. bHaveErrors = true;
  650. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to process entry. (hr=%s)", stringify(hr, true).c_str());
  651. if (hr == MAPI_E_STORE_FULL) {
  652. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Disk full or over quota.");
  653. goto exit;
  654. }
  655. continue;
  656. }
  657. }
  658. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Done processing batch");
  659. } while (ptrRowSet.size() == 50);
  660. exit:
  661. if (hr == hrSuccess && bHaveErrors)
  662. hr = MAPI_W_PARTIAL_COMPLETION;
  663. m_lpLogger->SetFolder(strFolderRestore);
  664. return hr;
  665. }
  666. /**
  667. * Purge a set of archives. Purging an archive is defined as deleting all
  668. * messages that are older than a set amount of days.
  669. *
  670. * @param[in] lstArchives The list of archives to purge.
  671. */
  672. HRESULT ArchiveControlImpl::PurgeArchives(const ObjectEntryList &lstArchives)
  673. {
  674. HRESULT hr = hrSuccess;
  675. bool bErrorOccurred = false;
  676. KCHL::memory_ptr<SRestriction> lpRestriction;
  677. SPropValue sPropCreationTime;
  678. ULARGE_INTEGER li;
  679. SRowSetPtr ptrRowSet;
  680. static constexpr const SizedSPropTagArray(2, sptaFolderProps) =
  681. {2, {PR_ENTRYID, PR_DISPLAY_NAME}};
  682. enum {IDX_ENTRYID, IDX_DISPLAY_NAME};
  683. // Create the common restriction that determines which messages are old enough to purge.
  684. li.LowPart = m_ftCurrent.dwLowDateTime;
  685. li.HighPart = m_ftCurrent.dwHighDateTime;
  686. li.QuadPart -= (m_ulPurgeAfter * _DAY);
  687. sPropCreationTime.ulPropTag = PR_MESSAGE_DELIVERY_TIME;
  688. sPropCreationTime.Value.ft.dwLowDateTime = li.LowPart;
  689. sPropCreationTime.Value.ft.dwHighDateTime = li.HighPart;
  690. hr = ECPropertyRestriction(RELOP_LT, PR_MESSAGE_DELIVERY_TIME, &sPropCreationTime, ECRestriction::Cheap)
  691. .CreateMAPIRestriction(&~lpRestriction, ECRestriction::Cheap);
  692. if (hr != hrSuccess)
  693. goto exit;
  694. for (const auto &arc : lstArchives) {
  695. MsgStorePtr ptrArchiveStore;
  696. MAPIFolderPtr ptrArchiveRoot;
  697. ULONG ulType = 0;
  698. MAPITablePtr ptrFolderTable;
  699. SRowSetPtr ptrFolderRows;
  700. hr = m_ptrSession->OpenStore(arc.sStoreEntryId, &~ptrArchiveStore);
  701. if (hr != hrSuccess) {
  702. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open archive. (entryid=%s, hr=%s)", arc.sStoreEntryId.tostring().c_str(), stringify(hr, true).c_str());
  703. bErrorOccurred = true;
  704. continue;
  705. }
  706. // Purge root of archive
  707. hr = PurgeArchiveFolder(ptrArchiveStore, arc.sItemEntryId, lpRestriction);
  708. if (hr != hrSuccess) {
  709. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to purge archive root. (entryid=%s, hr=%s)", arc.sItemEntryId.tostring().c_str(), stringify(hr, true).c_str());
  710. bErrorOccurred = true;
  711. continue;
  712. }
  713. // Get all subfolders and purge those as well.
  714. hr = ptrArchiveStore->OpenEntry(arc.sItemEntryId.size(), arc.sItemEntryId, &ptrArchiveRoot.iid(), MAPI_BEST_ACCESS|fMapiDeferredErrors, &ulType, &~ptrArchiveRoot);
  715. if (hr != hrSuccess) {
  716. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open archive root. (entryid=%s, hr=%s)", arc.sItemEntryId.tostring().c_str(), stringify(hr, true).c_str());
  717. bErrorOccurred = true;
  718. continue;
  719. }
  720. hr = ptrArchiveRoot->GetHierarchyTable(CONVENIENT_DEPTH | fMapiDeferredErrors, &~ptrFolderTable);
  721. if (hr != hrSuccess) {
  722. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get archive hierarchy table. (hr=%s)", stringify(hr, true).c_str());
  723. bErrorOccurred = true;
  724. continue;
  725. }
  726. hr = ptrFolderTable->SetColumns(sptaFolderProps, TBL_BATCH);
  727. if (hr != hrSuccess) {
  728. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to select folder table columns. (hr=%s)", stringify(hr, true).c_str());
  729. bErrorOccurred = true;
  730. continue;
  731. }
  732. while (true) {
  733. hr = ptrFolderTable->QueryRows(50, 0, &ptrFolderRows);
  734. if (hr != hrSuccess) {
  735. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get rows from folder table. (hr=%s)", stringify(hr, true).c_str());
  736. goto exit;
  737. }
  738. for (ULONG i = 0; i < ptrFolderRows.size(); ++i) {
  739. ScopedFolderLogging sfl(m_lpLogger, ptrFolderRows[i].lpProps[IDX_DISPLAY_NAME].ulPropTag == PR_DISPLAY_NAME ? ptrFolderRows[i].lpProps[IDX_DISPLAY_NAME].Value.LPSZ : _T("<Unnamed>"));
  740. hr = PurgeArchiveFolder(ptrArchiveStore, ptrFolderRows[i].lpProps[IDX_ENTRYID].Value.bin, lpRestriction);
  741. if (hr != hrSuccess) {
  742. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to purge archive folder. (entryid=%s, hr=%s)", bin2hex(ptrFolderRows[i].lpProps[IDX_ENTRYID].Value.bin.cb, ptrFolderRows[i].lpProps[IDX_ENTRYID].Value.bin.lpb).c_str(), stringify(hr, true).c_str());
  743. bErrorOccurred = true;
  744. }
  745. }
  746. if (ptrFolderRows.size() < 50)
  747. break;
  748. }
  749. }
  750. exit:
  751. if (hr == hrSuccess && bErrorOccurred)
  752. hr = MAPI_W_PARTIAL_COMPLETION;
  753. return hr;
  754. }
  755. /**
  756. * Purge an archive folder.
  757. *
  758. * @param[in] ptrArchive The archive store containing the folder to purge.
  759. * @param[in] folderEntryID The entryid of the folder to purge.
  760. * @param[in] lpRestriction The restriction to use to determine which messages to delete.
  761. */
  762. HRESULT ArchiveControlImpl::PurgeArchiveFolder(MsgStorePtr &ptrArchive, const entryid_t &folderEntryID, const LPSRestriction lpRestriction)
  763. {
  764. HRESULT hr;
  765. ULONG ulType = 0;
  766. MAPIFolderPtr ptrFolder;
  767. MAPITablePtr ptrContentsTable;
  768. std::list<entryid_t> lstEntries;
  769. SRowSetPtr ptrRows;
  770. EntryListPtr ptrEntryList;
  771. ULONG ulIdx = 0;
  772. static constexpr const SizedSPropTagArray(1, sptaTableProps) = {1, {PR_ENTRYID}};
  773. hr = ptrArchive->OpenEntry(folderEntryID.size(), folderEntryID, &ptrFolder.iid(), MAPI_BEST_ACCESS | fMapiDeferredErrors, &ulType, &~ptrFolder);
  774. if (hr != hrSuccess) {
  775. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open archive folder. (entryid=%s, hr=%s)", folderEntryID.tostring().c_str(), stringify(hr, true).c_str());
  776. return hr;
  777. }
  778. hr = ptrFolder->GetContentsTable(fMapiDeferredErrors, &~ptrContentsTable);
  779. if (hr != hrSuccess) {
  780. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open contents table. (hr=%s)", stringify(hr, true).c_str());
  781. return hr;
  782. }
  783. hr = ptrContentsTable->SetColumns(sptaTableProps, TBL_BATCH);
  784. if (hr != hrSuccess) {
  785. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to select table columns. (hr=%s)", stringify(hr, true).c_str());
  786. return hr;
  787. }
  788. hr = ptrContentsTable->Restrict(lpRestriction, TBL_BATCH);
  789. if (hr != hrSuccess) {
  790. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to restrict contents table. (hr=%s)", stringify(hr, true).c_str());
  791. return hr;
  792. }
  793. while (true) {
  794. hr = ptrContentsTable->QueryRows(50, 0, &ptrRows);
  795. if (hr != hrSuccess) {
  796. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get rows from contents table. (hr=%s)", stringify(hr, true).c_str());
  797. return hr;
  798. }
  799. for (ULONG i = 0; i < ptrRows.size(); ++i)
  800. lstEntries.push_back(ptrRows[i].lpProps[0].Value.bin);
  801. if (ptrRows.size() < 50)
  802. break;
  803. }
  804. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Purging %zu messaged from archive folder", lstEntries.size());
  805. hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~ptrEntryList);
  806. if (hr != hrSuccess)
  807. return hr;
  808. hr = MAPIAllocateMore(lstEntries.size() * sizeof(SBinary), ptrEntryList, (LPVOID*)&ptrEntryList->lpbin);
  809. if (hr != hrSuccess)
  810. return hr;
  811. ptrEntryList->cValues = lstEntries.size();
  812. for (const auto &e : lstEntries) {
  813. ptrEntryList->lpbin[ulIdx].cb = e.size();
  814. ptrEntryList->lpbin[ulIdx++].lpb = e;
  815. }
  816. hr = ptrFolder->DeleteMessages(ptrEntryList, 0, NULL, 0);
  817. if (hr != hrSuccess) {
  818. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to delete %u messages. (hr=%s)", ptrEntryList->cValues, stringify(hr, true).c_str());
  819. return hr;
  820. }
  821. return hrSuccess;
  822. }
  823. /**
  824. * Cleanup an archive.
  825. *
  826. * @param[in] archiveEntry SObjectEntry specifyinf the archive to cleanup
  827. * @param[in] lpUserStore The primary store, used to check the references
  828. * @param[in] lpRestriction The restriction that's used to make sure the archived items are old enough.
  829. */
  830. HRESULT ArchiveControlImpl::CleanupArchive(const SObjectEntry &archiveEntry, IMsgStore* lpUserStore, LPSRestriction lpRestriction)
  831. {
  832. HRESULT hr;
  833. SPropValuePtr ptrPropVal;
  834. EntryIDSet setRefs;
  835. EntryIDSet setEntries;
  836. EntryIDSet setDead;
  837. ArchiveHelperPtr ptrArchiveHelper;
  838. MAPIFolderPtr ptrArchiveFolder;
  839. ECFolderIterator iEnd;
  840. hr = ArchiveHelper::Create(m_ptrSession, archiveEntry, m_lpLogger, &ptrArchiveHelper);
  841. if (hr != hrSuccess)
  842. return hr;
  843. hr = ptrArchiveHelper->GetArchiveFolder(true, &~ptrArchiveFolder);
  844. if (hr != hrSuccess)
  845. return hr;
  846. if (m_cleanupAction == caStore) {
  847. // If the cleanup action is store, we need to perform the hierarchy cleanup
  848. // before cleaning up messages so the hierarchy gets preserved.
  849. hr = CleanupHierarchy(ptrArchiveHelper, ptrArchiveFolder, lpUserStore);
  850. if (hr != hrSuccess) {
  851. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to cleanup hierarchy.");
  852. return hr;
  853. }
  854. }
  855. // Get the archive store GUID (PR_STORE_RECORD_KEY)
  856. hr = HrGetOneProp(ptrArchiveHelper->GetMsgStore(), PR_STORE_RECORD_KEY, &~ptrPropVal);
  857. if (hr != hrSuccess) {
  858. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get store GUID of archive store.");
  859. return hr;
  860. }
  861. if (ptrPropVal->Value.bin.cb != sizeof(GUID)) {
  862. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Store record key size does not match that of a GUID. size=%u", ptrPropVal->Value.bin.cb);
  863. return MAPI_E_CORRUPT_DATA;
  864. }
  865. // Get a set of all primary messages that have a reference to this archive.
  866. hr = GetAllReferences(lpUserStore, (LPGUID)ptrPropVal->Value.bin.lpb, &setRefs);
  867. if (hr != hrSuccess) {
  868. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get all references from primary store. (hr=0x%08x)", hr);
  869. return hr;
  870. }
  871. hr = GetAllEntries(ptrArchiveHelper, ptrArchiveFolder, lpRestriction, &setEntries);
  872. if (hr != hrSuccess) {
  873. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get all entries from archive store. (hr=0x%08x)", hr);
  874. return hr;
  875. }
  876. // We now have a set containing the entryids of all messages in the archive and a set containing all
  877. // references to archives in the primary store, which are those same entryids.
  878. // We simply check which entries are in the set of entries from the archive and not in the set of
  879. // entries in the primary store. Those can be deleted (or stored).
  880. //The difference of two sets is formed by the elements that are present in the first set, but not in
  881. //the second one. Notice that this is a directional operation.
  882. std::set_difference(setEntries.begin(), setEntries.end(), setRefs.begin(), setRefs.end(), std::inserter(setDead, setDead.begin()));
  883. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Found %zu dead entries in archive.", setDead.size());
  884. if (m_cleanupAction == caNone) {
  885. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "cleanup_action is set to none, therefore skipping cleanup action.");
  886. return hr;
  887. }
  888. if (!setDead.empty()) {
  889. if (m_cleanupAction == caStore)
  890. hr = MoveAndDetachMessages(ptrArchiveHelper, ptrArchiveFolder, setDead);
  891. else
  892. hr = DeleteMessages(ptrArchiveFolder, setDead);
  893. }
  894. if (m_cleanupAction != caDelete)
  895. return hr;
  896. // If the cleanup action is delete, we need to cleanup the hierarchy after cleaning the
  897. // messages because we won't delete non-empty folders. So we want to get rid of the
  898. // messages first.
  899. hr = CleanupHierarchy(ptrArchiveHelper, ptrArchiveFolder, lpUserStore);
  900. if (hr != hrSuccess)
  901. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to cleanup hierarchy.");
  902. return hr;
  903. }
  904. /**
  905. * Get all references to archived items from the primary store. A reference is the entryid of
  906. * the archived messages.
  907. *
  908. * @param[in] lpUserStore The primary store containing the references.
  909. * @param[in] lpArchiveGuid The GUID of the archive store for which to get the references.
  910. * @param[out] lpReferences An EntryIDSet containing all references.
  911. */
  912. HRESULT ArchiveControlImpl::GetAllReferences(LPMDB lpUserStore, LPGUID lpArchiveGuid, EntryIDSet *lpReferences)
  913. {
  914. HRESULT hr;
  915. EntryIDSet setRefs;
  916. SPropValuePtr ptrPropVal;
  917. ULONG ulType = 0;
  918. MAPIFolderPtr ptrIpmSubtree;
  919. ECFolderIterator iEnd;
  920. // Find the primary store IPM subtree
  921. hr = HrGetOneProp(lpUserStore, PR_IPM_SUBTREE_ENTRYID, &~ptrPropVal);
  922. if (hr != hrSuccess) {
  923. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to locate ipm subtree of primary store. (hr=0x%08x)", hr);
  924. return hr;
  925. }
  926. hr = lpUserStore->OpenEntry(ptrPropVal->Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrPropVal->Value.bin.lpb), &ptrIpmSubtree.iid(), 0, &ulType, &~ptrIpmSubtree);
  927. if (hr != hrSuccess) {
  928. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open ipm subtree of primary store. (hr=0x%08x)", hr);
  929. return hr;
  930. }
  931. hr = AppendAllReferences(ptrIpmSubtree, lpArchiveGuid, &setRefs);
  932. if (hr != hrSuccess) {
  933. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get all references from the ipm subtree. (hr=0x%08x)", hr);
  934. return hr;
  935. }
  936. try {
  937. for (ECFolderIterator i = ECFolderIterator(ptrIpmSubtree, fMapiDeferredErrors, 0); i != iEnd; ++i) {
  938. hr = AppendAllReferences(*i, lpArchiveGuid, &setRefs);
  939. if (hr != hrSuccess) {
  940. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get all references from primary folder. (hr=0x%08x)", hr);
  941. return hr;
  942. }
  943. }
  944. } catch (const HrException &he) {
  945. hr = he.hr();
  946. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to iterate primary folders. (hr=0x%08x)", hr);
  947. return hr;
  948. }
  949. lpReferences->swap(setRefs);
  950. return hrSuccess;
  951. }
  952. /**
  953. * Get all references to archived items from a primary folder and add them to the
  954. * passed set.
  955. *
  956. * @param[in] lpUserStore The primary store containing the references.
  957. * @param[in] lpArchiveGuid The GUID of the archive store for which to get the references.
  958. * @param[out] lpReferences The EntryIDSet to add the references to.
  959. */
  960. HRESULT ArchiveControlImpl::AppendAllReferences(LPMAPIFOLDER lpFolder, LPGUID lpArchiveGuid, EntryIDSet *lpReferences)
  961. {
  962. HRESULT hr = hrSuccess;
  963. BYTE prefixData[4 + sizeof(GUID)] = {0};
  964. static constexpr const ULONG ulFlagArray[] = {0, SHOW_SOFT_DELETES};
  965. SizedSPropTagArray(1, sptaContentProps) = {1, {PT_NULL}};
  966. PROPMAP_START(1)
  967. PROPMAP_NAMED_ID(ITEM_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidItemEntryIds)
  968. PROPMAP_INIT(lpFolder)
  969. sptaContentProps.aulPropTag[0] = PROP_ITEM_ENTRYIDS;
  970. memcpy(prefixData + 4, lpArchiveGuid, sizeof(GUID));
  971. for (size_t i = 0; i < ARRAY_SIZE(ulFlagArray); ++i) {
  972. MAPITablePtr ptrTable;
  973. hr = lpFolder->GetContentsTable(ulFlagArray[i], &~ptrTable);
  974. if (hr != hrSuccess)
  975. goto exitpm;
  976. hr = ptrTable->SetColumns(sptaContentProps, TBL_BATCH);
  977. if (hr != hrSuccess)
  978. goto exitpm;
  979. while (true) {
  980. SRowSetPtr ptrRows;
  981. const ULONG batch_size = 128;
  982. hr = ptrTable->QueryRows(batch_size, 0, &ptrRows);
  983. if (hr != hrSuccess)
  984. goto exitpm;
  985. for (SRowSetPtr::size_type j = 0; j < ptrRows.size(); ++j) {
  986. if (PROP_TYPE(ptrRows[j].lpProps[0].ulPropTag) == PT_ERROR)
  987. continue;
  988. for (ULONG k = 0; k < ptrRows[j].lpProps[0].Value.MVbin.cValues; ++k) {
  989. if (ptrRows[j].lpProps[0].Value.MVbin.lpbin[k].cb >= sizeof(prefixData) &&
  990. memcmp(ptrRows[j].lpProps[0].Value.MVbin.lpbin[k].lpb, prefixData, sizeof(prefixData)) == 0) {
  991. lpReferences->insert(ptrRows[j].lpProps[0].Value.MVbin.lpbin[k]);
  992. }
  993. }
  994. }
  995. if (ptrRows.size() < batch_size)
  996. break;
  997. }
  998. }
  999. exitpm:
  1000. return hr;
  1001. }
  1002. /**
  1003. * Get the entryid of almost all messages in an archive store. Everything that's
  1004. * below the special folder root is excluded because those don't necessarily get
  1005. * referenced from the primary store anymore.
  1006. *
  1007. * @param[in] ptrArchiveHelper The ArchiverHelper instance for the archive to process.
  1008. * @param[in] lpArchive The root of the archive.
  1009. * @param[in] lpRestriction The restriction that's used to make sure the archived items are old enough.
  1010. * @param[out] lpEntryies An EntryIDSet containing all the entryids.
  1011. */
  1012. HRESULT ArchiveControlImpl::GetAllEntries(ArchiveHelperPtr ptrArchiveHelper, LPMAPIFOLDER lpArchive, LPSRestriction lpRestriction, EntryIDSet *lpEntries)
  1013. {
  1014. HRESULT hr;
  1015. EntryIDSet setEntries;
  1016. ECFolderIterator iEnd;
  1017. EntryIDSet setFolderExcludes;
  1018. MAPIFolderPtr ptrFolder;
  1019. hr = AppendAllEntries(lpArchive, lpRestriction, &setEntries);
  1020. if (hr != hrSuccess) {
  1021. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get all entries from the root archive folder. (hr=0x%08x)", hr);
  1022. return hr;
  1023. }
  1024. // Exclude everything below the special folder root because that's were we store messages
  1025. // that have not references to the primary store.
  1026. hr = ptrArchiveHelper->GetSpecialsRootFolder(&~ptrFolder);
  1027. if (hr == hrSuccess)
  1028. hr = AppendFolderEntries(ptrFolder, &setFolderExcludes);
  1029. ptrFolder.reset();
  1030. try {
  1031. for (ECFolderIterator i = ECFolderIterator(lpArchive, fMapiDeferredErrors, 0); i != iEnd; ++i) {
  1032. SPropValuePtr ptrProp;
  1033. hr = HrGetOneProp(*i, PR_ENTRYID, &~ptrProp);
  1034. if (hr != hrSuccess)
  1035. return hr;
  1036. if (setFolderExcludes.find(ptrProp->Value.bin) != setFolderExcludes.end()) {
  1037. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Skipping special folder");
  1038. continue;
  1039. }
  1040. hr = AppendAllEntries(*i, lpRestriction, &setEntries);
  1041. if (hr != hrSuccess) {
  1042. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get all references from archive folder. (hr=0x%08x)", hr);
  1043. return hr;
  1044. }
  1045. }
  1046. } catch (const HrException &he) {
  1047. hr = he.hr();
  1048. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to iterate archive folders. (hr=0x%08x)", hr);
  1049. return hr;
  1050. }
  1051. lpEntries->swap(setEntries);
  1052. return hrSuccess;
  1053. }
  1054. /**
  1055. * Get the entryid of all messages in an archive folder and append them to the passed set.
  1056. *
  1057. * @param[in] ptrArchiveHelper The ArchiverHelper instance for the archive to process.
  1058. * @param[in] lpArchive The root of the archive.
  1059. * @param[in] lpRestriction The restriction that's used to make sure the archived items are old enough.
  1060. * @param[in,out] lpEntryies The EntryIDSet to add the items to.
  1061. */
  1062. HRESULT ArchiveControlImpl::AppendAllEntries(LPMAPIFOLDER lpArchive, LPSRestriction lpRestriction, EntryIDSet *lpEntries)
  1063. {
  1064. HRESULT hr = hrSuccess;
  1065. MAPITablePtr ptrTable;
  1066. ECAndRestriction resContent;
  1067. static constexpr const SizedSPropTagArray(1, sptaContentProps) = {1, {PR_ENTRYID}};
  1068. PROPMAP_START(1)
  1069. PROPMAP_NAMED_ID(REF_ITEM_ENTRYID, PT_BINARY, PSETID_Archive, dispidRefItemEntryId)
  1070. PROPMAP_INIT(lpArchive)
  1071. resContent += ECExistRestriction(PROP_REF_ITEM_ENTRYID);
  1072. if (lpRestriction)
  1073. resContent += ECRawRestriction(lpRestriction, ECRestriction::Cheap);
  1074. hr = lpArchive->GetContentsTable(0, &~ptrTable);
  1075. if (hr != hrSuccess)
  1076. goto exitpm;
  1077. hr = ptrTable->SetColumns(sptaContentProps, TBL_BATCH);
  1078. if (hr != hrSuccess)
  1079. goto exitpm;
  1080. hr = resContent.RestrictTable(ptrTable);
  1081. if (hr != hrSuccess)
  1082. goto exitpm;
  1083. while (true) {
  1084. SRowSetPtr ptrRows;
  1085. const ULONG batch_size = 128;
  1086. hr = ptrTable->QueryRows(batch_size, 0, &ptrRows);
  1087. if (hr != hrSuccess)
  1088. goto exitpm;
  1089. for (SRowSetPtr::size_type i = 0; i < ptrRows.size(); ++i) {
  1090. if (PROP_TYPE(ptrRows[i].lpProps[0].ulPropTag) == PT_ERROR) {
  1091. hr = ptrRows[i].lpProps[0].Value.err;
  1092. goto exitpm;
  1093. }
  1094. lpEntries->insert(ptrRows[i].lpProps[0].Value.bin);
  1095. }
  1096. if (ptrRows.size() < batch_size)
  1097. break;
  1098. }
  1099. exitpm:
  1100. return hr;
  1101. }
  1102. /**
  1103. * Cleanup the archive hierarchy. This works by going through the hierarchy
  1104. * and check for each folder if the back reference still works. If it doesn't
  1105. * the folder should be deleted or moved to the deleted items folder.
  1106. * When the cleanup_action is 'delete', the folder will only be deleted if
  1107. * it's empty.
  1108. * If the cleanup action is 'store', the folder will be moved to the deleted
  1109. * items folder as is, leaving the hierarchy in tact.
  1110. *
  1111. * @param[in] ptrArchiveHelper The ArchiverHelper instance for the archive to process.
  1112. * @param[in] lpArchiveRoot The root of the archive.
  1113. * @param[out] lpUserStore The users primary store.
  1114. */
  1115. HRESULT ArchiveControlImpl::CleanupHierarchy(ArchiveHelperPtr ptrArchiveHelper, LPMAPIFOLDER lpArchiveRoot, LPMDB lpUserStore)
  1116. {
  1117. HRESULT hr = hrSuccess;
  1118. MAPITablePtr ptrTable;
  1119. static constexpr const SizedSSortOrderSet(1, ssosHierarchy) = {1, 0, 0, {PR_DEPTH, TABLE_SORT_ASCEND}};
  1120. SizedSPropTagArray(5, sptaHierarchyProps) = {5, {PR_NULL, PR_ENTRYID, PR_CONTENT_COUNT, PR_FOLDER_CHILD_COUNT, PR_DISPLAY_NAME}};
  1121. enum {IDX_REF_ITEM_ENTRYID, IDX_ENTRYID, IDX_CONTENT_COUNT, IDX_FOLDER_CHILD_COUNT, IDX_DISPLAY_NAME};
  1122. PROPMAP_START(1)
  1123. PROPMAP_NAMED_ID(REF_ITEM_ENTRYID, PT_BINARY, PSETID_Archive, dispidRefItemEntryId)
  1124. PROPMAP_INIT(lpArchiveRoot)
  1125. sptaHierarchyProps.aulPropTag[IDX_REF_ITEM_ENTRYID] = PROP_REF_ITEM_ENTRYID;
  1126. hr = lpArchiveRoot->GetHierarchyTable(CONVENIENT_DEPTH, &~ptrTable);
  1127. if (hr != hrSuccess)
  1128. goto exitpm;
  1129. hr = ptrTable->SetColumns(sptaHierarchyProps, TBL_BATCH);
  1130. if (hr != hrSuccess)
  1131. goto exitpm;
  1132. hr = ECExistRestriction(PROP_REF_ITEM_ENTRYID)
  1133. .RestrictTable(ptrTable, TBL_BATCH);
  1134. if (hr != hrSuccess)
  1135. goto exitpm;
  1136. hr = ptrTable->SortTable(ssosHierarchy, TBL_BATCH);
  1137. if (hr != hrSuccess)
  1138. goto exitpm;
  1139. while (true) {
  1140. SRowSetPtr ptrRows;
  1141. hr = ptrTable->QueryRows(64, 0, &ptrRows);
  1142. if (hr != hrSuccess)
  1143. goto exitpm;
  1144. if (ptrRows.empty())
  1145. break;
  1146. for (SRowSetPtr::size_type i = 0; i < ptrRows.size(); ++i) {
  1147. ULONG ulType = 0;
  1148. MAPIFolderPtr ptrPrimaryFolder;
  1149. ScopedFolderLogging sfl(m_lpLogger, ptrRows[i].lpProps[IDX_DISPLAY_NAME].ulPropTag == PR_DISPLAY_NAME ? ptrRows[i].lpProps[IDX_DISPLAY_NAME].Value.LPSZ : _T("<Unnamed>"));
  1150. // If the cleanup action is delete, we don't want to delete a folder that's not empty because it might contain messages that
  1151. // have been moved in the primary store before the original folder was deleted. If we were to delete the folder in the archive
  1152. // we would lose that data.
  1153. // But if the cleanup action is store, we do want to move the folder with content so the hierarchy is preserved.
  1154. if (m_cleanupAction == caDelete) {
  1155. // The content count and folder child count should always exist. If not we'll skip the folder
  1156. // just to be safe.
  1157. if (PROP_TYPE(ptrRows[i].lpProps[IDX_CONTENT_COUNT].ulPropTag) == PT_ERROR) {
  1158. m_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to obtain folder content count. Skipping folder. (hr=0x%08x)", ptrRows[i].lpProps[IDX_CONTENT_COUNT].Value.err);
  1159. continue;
  1160. } else if (ptrRows[i].lpProps[IDX_CONTENT_COUNT].Value.l != 0) {
  1161. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Folder contains messages. Skipping folder.");
  1162. continue;
  1163. }
  1164. if (PROP_TYPE(ptrRows[i].lpProps[IDX_FOLDER_CHILD_COUNT].ulPropTag) == PT_ERROR) {
  1165. m_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to obtain folder child count. Skipping folder. (hr=0x%08x)", ptrRows[i].lpProps[IDX_FOLDER_CHILD_COUNT].Value.err);
  1166. continue;
  1167. } else if (ptrRows[i].lpProps[IDX_FOLDER_CHILD_COUNT].Value.l != 0) {
  1168. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Folder has subfolders in it. Skipping folder.");
  1169. continue;
  1170. }
  1171. }
  1172. hr = lpUserStore->OpenEntry(ptrRows[i].lpProps[IDX_REF_ITEM_ENTRYID].Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrRows[i].lpProps[IDX_REF_ITEM_ENTRYID].Value.bin.lpb),
  1173. &ptrPrimaryFolder.iid(), 0, &ulType, &~ptrPrimaryFolder);
  1174. if (hr == MAPI_E_NOT_FOUND) {
  1175. MAPIFolderPtr ptrArchiveFolder;
  1176. SPropValuePtr ptrProp;
  1177. hr = lpArchiveRoot->OpenEntry(ptrRows[i].lpProps[IDX_ENTRYID].Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrRows[i].lpProps[IDX_ENTRYID].Value.bin.lpb),
  1178. &ptrArchiveFolder.iid(), MAPI_MODIFY, &ulType, &~ptrArchiveFolder);
  1179. if (hr != hrSuccess)
  1180. goto exitpm;
  1181. // Check if we still have a back-ref
  1182. if (HrGetOneProp(ptrArchiveFolder, PROP_REF_ITEM_ENTRYID, &~ptrProp) != hrSuccess) {
  1183. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Back ref is gone. Folder is possibly moved to the deleted items already.");
  1184. continue;
  1185. }
  1186. // The primary folder does not exist anymore and this folder is empty. Time to get rid of it.
  1187. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Primary folder seems to have been deleted");
  1188. if (m_cleanupAction == caStore)
  1189. hr = MoveAndDetachFolder(ptrArchiveHelper, ptrArchiveFolder);
  1190. else
  1191. hr = DeleteFolder(ptrArchiveFolder);
  1192. if (hr != hrSuccess)
  1193. m_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to process dead folder. (hr=0x%08x)", hr);
  1194. }
  1195. if (hr != hrSuccess)
  1196. goto exitpm;
  1197. }
  1198. }
  1199. exitpm:
  1200. return hr;
  1201. }
  1202. /**
  1203. * Move a set of messages to the special 'Deleted Items' folder and remove their reference to a
  1204. * primary message that was deleted.
  1205. *
  1206. * @param[in] ptrArchiveHelper An ArchiveHelper object containing the archive store that's being processed.
  1207. * @param[in] lpArchiveFolder The archive folder containing the messages to move.
  1208. * @param[in] setEIDs The set with entryids of the messages to process.
  1209. */
  1210. HRESULT ArchiveControlImpl::MoveAndDetachMessages(ArchiveHelperPtr ptrArchiveHelper, LPMAPIFOLDER lpArchiveFolder, const EntryIDSet &setEIDs)
  1211. {
  1212. HRESULT hr;
  1213. MAPIFolderPtr ptrDelItemsFolder;
  1214. EntryListPtr ptrMessageList;
  1215. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Moving %zu messages to the special 'Deleted Items' folder...", setEIDs.size());
  1216. hr = ptrArchiveHelper->GetDeletedItemsFolder(&~ptrDelItemsFolder);
  1217. if (hr != hrSuccess) {
  1218. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get deleted items folder. (hr=0x%08x)", hr);
  1219. return hr;
  1220. }
  1221. hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~ptrMessageList);
  1222. if (hr != hrSuccess) {
  1223. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to allocate %zu bytes of memory. (hr=0x%08x)", sizeof(ENTRYLIST), hr);
  1224. return hr;
  1225. }
  1226. ptrMessageList->cValues = 0;
  1227. hr = MAPIAllocateMore(sizeof(SBinary) * setEIDs.size(), ptrMessageList, (LPVOID*)&ptrMessageList->lpbin);
  1228. if (hr != hrSuccess) {
  1229. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to allocate %zu bytes of memory. (hr=0x%08x)", sizeof(SBinary) * setEIDs.size(), hr);
  1230. return hr;
  1231. }
  1232. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Processing %zu messages", setEIDs.size());
  1233. for (const auto &e : setEIDs) {
  1234. ULONG ulType;
  1235. MAPIPropPtr ptrMessage;
  1236. MAPIPropHelperPtr ptrHelper;
  1237. hr = lpArchiveFolder->OpenEntry(e.size(), e, &ptrMessage.iid(), MAPI_MODIFY, &ulType, &~ptrMessage);
  1238. if (hr != hrSuccess) {
  1239. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open message. (hr=0x%08x)", hr);
  1240. return hr;
  1241. }
  1242. hr = MAPIPropHelper::Create(ptrMessage, &ptrHelper);
  1243. if (hr != hrSuccess) {
  1244. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to create helper object. (hr=0x%08x)", hr);
  1245. return hr;
  1246. }
  1247. hr = ptrHelper->ClearReference(true);
  1248. if (hr != hrSuccess) {
  1249. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to clear back reference. (hr=0x%08x)", hr);
  1250. return hr;
  1251. }
  1252. ptrMessageList->lpbin[ptrMessageList->cValues].cb = e.size();
  1253. ptrMessageList->lpbin[ptrMessageList->cValues++].lpb = e;
  1254. assert(ptrMessageList->cValues <= setEIDs.size());
  1255. }
  1256. hr = lpArchiveFolder->CopyMessages(ptrMessageList, &ptrDelItemsFolder.iid(), ptrDelItemsFolder, 0, NULL, MESSAGE_MOVE);
  1257. if (hr != hrSuccess) {
  1258. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to move messages. (hr=0x%08x)", hr);
  1259. return hr;
  1260. }
  1261. return hrSuccess;
  1262. }
  1263. /**
  1264. * Move a folder to the special 'Deleted Items' folder and remove its reference to the
  1265. * primary folder that was deleted.
  1266. *
  1267. * @param[in] ptrArchiveHelper An ArchiveHelper object containing the archive store that's being processed.
  1268. * @param[in] lpArchiveFolder The archive folder to move.
  1269. */
  1270. HRESULT ArchiveControlImpl::MoveAndDetachFolder(ArchiveHelperPtr ptrArchiveHelper, LPMAPIFOLDER lpArchiveFolder)
  1271. {
  1272. HRESULT hr;
  1273. SPropValuePtr ptrEntryID;
  1274. MAPIFolderPtr ptrDelItemsFolder;
  1275. MAPIPropHelperPtr ptrHelper;
  1276. ECFolderIterator iEnd;
  1277. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Moving folder to the special 'Deleted Items' folder...");
  1278. hr = HrGetOneProp(lpArchiveFolder, PR_ENTRYID, &~ptrEntryID);
  1279. if (hr != hrSuccess) {
  1280. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get folder entryid. (hr=0x%08x)", hr);
  1281. return hr;
  1282. }
  1283. hr = ptrArchiveHelper->GetDeletedItemsFolder(&~ptrDelItemsFolder);
  1284. if (hr != hrSuccess) {
  1285. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get deleted items folder. (hr=0x%08x)", hr);
  1286. return hr;
  1287. }
  1288. hr = MAPIPropHelper::Create(MAPIPropPtr(lpArchiveFolder, true), &ptrHelper);
  1289. if (hr != hrSuccess) {
  1290. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to create helper object. (hr=0x%08x)", hr);
  1291. return hr;
  1292. }
  1293. hr = ptrHelper->ClearReference(true);
  1294. if (hr != hrSuccess) {
  1295. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to clear back reference. (hr=0x%08x)", hr);
  1296. return hr;
  1297. }
  1298. // Get rid of references of all subfolders
  1299. try {
  1300. for (ECFolderIterator i = ECFolderIterator(lpArchiveFolder, fMapiDeferredErrors, 0); i != iEnd; ++i) {
  1301. MAPIPropHelperPtr ptrSubHelper;
  1302. hr = MAPIPropHelper::Create(MAPIPropPtr(*i, true), &ptrSubHelper);
  1303. if (hr != hrSuccess)
  1304. return hr;
  1305. hr = ptrSubHelper->ClearReference(true);
  1306. if (hr != hrSuccess)
  1307. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Failed to clean reference of subfolder.");
  1308. }
  1309. } catch (const HrException &he) {
  1310. hr = he.hr();
  1311. m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to iterate folders. (hr=0x%08x)", hr);
  1312. return hr;
  1313. }
  1314. hr = lpArchiveFolder->CopyFolder(ptrEntryID->Value.bin.cb, (LPENTRYID)ptrEntryID->Value.bin.lpb, &ptrDelItemsFolder.iid(), ptrDelItemsFolder, NULL, 0, NULL, FOLDER_MOVE);
  1315. if (hr != hrSuccess) {
  1316. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to move folder. (hr=0x%08x)", hr);
  1317. return hr;
  1318. }
  1319. return hrSuccess;
  1320. }
  1321. /**
  1322. * Delete the messages in setEIDs from the folder lpArchiveFolder.
  1323. *
  1324. * @param[in] lpArchiveFolder The folder to delete the messages from.
  1325. * @param[in] setEIDs The set of entryids of the messages to delete.
  1326. */
  1327. HRESULT ArchiveControlImpl::DeleteMessages(LPMAPIFOLDER lpArchiveFolder, const EntryIDSet &setEIDs)
  1328. {
  1329. HRESULT hr;
  1330. EntryListPtr ptrMessageList;
  1331. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Deleting %zu messages...", setEIDs.size());
  1332. hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~ptrMessageList);
  1333. if (hr != hrSuccess) {
  1334. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to allocate %zu bytes of memory. (hr=0x%08x)", sizeof(ENTRYLIST), hr);
  1335. return hr;
  1336. }
  1337. ptrMessageList->cValues = 0;
  1338. hr = MAPIAllocateMore(sizeof(SBinary) * setEIDs.size(), ptrMessageList, (LPVOID*)&ptrMessageList->lpbin);
  1339. if (hr != hrSuccess) {
  1340. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to allocate %zu bytes of memory. (hr=0x%08x)", sizeof(SBinary) * setEIDs.size(), hr);
  1341. return hr;
  1342. }
  1343. m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Processing %zu messages", setEIDs.size());
  1344. for (const auto &e : setEIDs) {
  1345. ptrMessageList->lpbin[ptrMessageList->cValues].cb = e.size();
  1346. ptrMessageList->lpbin[ptrMessageList->cValues++].lpb = e;
  1347. }
  1348. return lpArchiveFolder->DeleteMessages(ptrMessageList, 0, NULL, 0);
  1349. }
  1350. /**
  1351. * Delete the folder specified by lpArchiveFolder
  1352. *
  1353. * @param[in] lpArchiveFolder Folder to delete.
  1354. */
  1355. HRESULT ArchiveControlImpl::DeleteFolder(LPMAPIFOLDER lpArchiveFolder)
  1356. {
  1357. HRESULT hr;
  1358. SPropValuePtr ptrEntryId;
  1359. m_lpLogger->Log(EC_LOGLEVEL_INFO, "Deleting folder...");
  1360. hr = HrGetOneProp(lpArchiveFolder, PR_ENTRYID, &~ptrEntryId);
  1361. if (hr != hrSuccess) {
  1362. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get folder entryid (hr=0x%08x)", hr);
  1363. return hr;
  1364. }
  1365. // Delete yourself!
  1366. hr = lpArchiveFolder->DeleteFolder(ptrEntryId->Value.bin.cb, (LPENTRYID)ptrEntryId->Value.bin.lpb, 0, NULL, DEL_FOLDERS|DEL_MESSAGES|DEL_ASSOCIATED);
  1367. if (FAILED(hr)) {
  1368. m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to delete folder (hr=0x%08x)", hr);
  1369. return hr;
  1370. } else if (hr != hrSuccess)
  1371. m_lpLogger->Log(EC_LOGLEVEL_WARNING, "Folder only got partially deleted (hr=0x%08x)", hr);
  1372. return hr;
  1373. }
  1374. /**
  1375. * Append the entryid of the passed folder and all its subfolder to a list.
  1376. * @param[in] lpBase The folder to start processing.
  1377. * @param[out] lpEntries The returned set of entryids.
  1378. */
  1379. HRESULT ArchiveControlImpl::AppendFolderEntries(LPMAPIFOLDER lpBase, EntryIDSet *lpEntries)
  1380. {
  1381. HRESULT hr;
  1382. SPropValuePtr ptrProp;
  1383. MAPITablePtr ptrTable;
  1384. static constexpr const SizedSPropTagArray(1, sptaTableProps) = {1, {PR_ENTRYID}};
  1385. hr = HrGetOneProp(lpBase, PR_ENTRYID, &~ptrProp);
  1386. if (hr != hrSuccess)
  1387. return hr;
  1388. lpEntries->insert(ptrProp->Value.bin);
  1389. hr = lpBase->GetHierarchyTable(CONVENIENT_DEPTH, &~ptrTable);
  1390. if (hr != hrSuccess)
  1391. return hr;
  1392. hr = ptrTable->SetColumns(sptaTableProps, TBL_BATCH);
  1393. if (hr != hrSuccess)
  1394. return hr;
  1395. while (true) {
  1396. SRowSetPtr ptrRows;
  1397. hr = ptrTable->QueryRows(128, 0, &ptrRows);
  1398. if (hr != hrSuccess)
  1399. return hr;
  1400. if (ptrRows.empty())
  1401. break;
  1402. for (SRowSetPtr::size_type i = 0; i < ptrRows.size(); ++i)
  1403. lpEntries->insert(ptrRows[i].lpProps[0].Value.bin);
  1404. }
  1405. return hrSuccess;
  1406. }
  1407. /**
  1408. * This method checks the settings to see if they're safe when performing
  1409. * a cleanup run. It's unsafe to run a cleanup when the delete operation is
  1410. * enabled, and the cleanup doesn't check the purge_after option or if the
  1411. * purge_after option is set to 0.
  1412. *
  1413. * See ZCP-10571.
  1414. */
  1415. HRESULT ArchiveControlImpl::CheckSafeCleanupSettings()
  1416. {
  1417. int loglevel = (m_bForceCleanup ? EC_LOGLEVEL_WARNING : EC_LOGLEVEL_FATAL);
  1418. if (m_bDeleteEnable && !m_bCleanupFollowPurgeAfter) {
  1419. m_lpLogger->Log(loglevel, "'delete_enable' is set to '%s' and 'cleanup_follow_purge_after' is set to '%s'",
  1420. m_lpConfig->GetSetting("delete_enable", "", "no"),
  1421. m_lpConfig->GetSetting("cleanup_follow_purge_after", "", "no"));
  1422. m_lpLogger->Log(loglevel, "This can cause messages to be deleted from the archive while they shouldn't be deleted.");
  1423. if (!m_bForceCleanup) {
  1424. m_lpLogger->Log(loglevel, "Please correct your configuration or pass '--force-cleanup' at the commandline if you");
  1425. m_lpLogger->Log(loglevel, "know what you're doing (not recommended).");
  1426. return MAPI_E_UNABLE_TO_COMPLETE;
  1427. }
  1428. m_lpLogger->Log(loglevel, "User forced continuation!");
  1429. }
  1430. else if (m_bDeleteEnable && m_bCleanupFollowPurgeAfter && m_ulPurgeAfter == 0) {
  1431. m_lpLogger->Log(loglevel, "'delete_enable' is set to '%s' and 'cleanup_follow_purge_after' is set to '%s'",
  1432. m_lpConfig->GetSetting("delete_enable", "", "no"),
  1433. m_lpConfig->GetSetting("cleanup_follow_purge_after", "", "no"));
  1434. m_lpLogger->Log(loglevel, "but 'purge_after' is set to '0'");
  1435. m_lpLogger->Log(loglevel, "This can cause messages to be deleted from the archive while they shouldn't be deleted.");
  1436. if (!m_bForceCleanup) {
  1437. m_lpLogger->Log(loglevel, "Please correct your configuration or pass '--force-cleanup' at the commandline if you");
  1438. m_lpLogger->Log(loglevel, "know what you're doing (not recommended).");
  1439. return MAPI_E_UNABLE_TO_COMPLETE;
  1440. }
  1441. m_lpLogger->Log(loglevel, "User forced continuation!");
  1442. }
  1443. return hrSuccess;
  1444. }
  1445. } /* namespace */