userutil.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  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/zcdefs.h>
  18. #include <kopano/platform.h>
  19. #include <utility>
  20. #include <mapi.h>
  21. #include <mapiutil.h>
  22. #include <kopano/ECLogger.h>
  23. #include <kopano/memory.hpp>
  24. #include <kopano/userutil.h>
  25. #include <kopano/charset/utf8string.h>
  26. #include <kopano/charset/convert.h>
  27. #include <kopano/ECDefs.h>
  28. #include <kopano/ECGuid.h>
  29. #include <kopano/IECServiceAdmin.h>
  30. #include <edkmdb.h>
  31. #include <edkguid.h>
  32. #include <kopano/IECLicense.h>
  33. #include <kopano/CommonUtil.h>
  34. #include <kopano/ECRestriction.h>
  35. #include <kopano/mapi_ptr.h>
  36. #include <kopano/mapiguidext.h>
  37. #include <kopano/Util.h>
  38. using namespace std;
  39. namespace KC {
  40. typedef KCHL::object_ptr<IECLicense, IID_IECLicense> ECLicensePtr;
  41. class servername _kc_final {
  42. public:
  43. servername(LPCTSTR lpszName): m_strName(lpszName) {}
  44. servername(const servername &other): m_strName(other.m_strName) {}
  45. servername& operator=(const servername &other) {
  46. if (&other != this)
  47. m_strName = other.m_strName;
  48. return *this;
  49. }
  50. LPTSTR c_str() const {
  51. return (LPTSTR)m_strName.c_str();
  52. }
  53. bool operator<(const servername &other) const {
  54. return wcscasecmp(m_strName.c_str(), other.m_strName.c_str()) < 0;
  55. }
  56. private:
  57. wstring m_strName;
  58. };
  59. static HRESULT GetMailboxDataPerServer(const char *lpszPath, const char *lpSSLKey, const char *lpSSLPass, DataCollector *lpCollector);
  60. static HRESULT GetMailboxDataPerServer(IMAPISession *lpSession, const char *lpszPath, DataCollector *lpCollector);
  61. static HRESULT UpdateServerList(IABContainer *lpContainer, std::set<servername> &listServers);
  62. class UserCountCollector _kc_final : public DataCollector {
  63. public:
  64. virtual HRESULT CollectData(LPMAPITABLE store_table) _kc_override;
  65. unsigned int result() const;
  66. private:
  67. unsigned int m_ulUserCount = 0;
  68. };
  69. template <typename string_type, ULONG prAccount>
  70. class UserListCollector _kc_final : public DataCollector {
  71. public:
  72. UserListCollector(IMAPISession *lpSession);
  73. virtual HRESULT GetRequiredPropTags(LPMAPIPROP prop, LPSPropTagArray *) const _kc_override;
  74. virtual HRESULT CollectData(LPMAPITABLE store_table) _kc_override;
  75. void swap_result(std::list<string_type> *lplstUsers);
  76. private:
  77. void push_back(LPSPropValue lpPropAccount);
  78. std::list<string_type> m_lstUsers;
  79. MAPISessionPtr m_ptrSession;
  80. };
  81. HRESULT DataCollector::GetRequiredPropTags(LPMAPIPROP /*lpProp*/, LPSPropTagArray *lppPropTagArray) const {
  82. static constexpr const SizedSPropTagArray(1, sptaDefaultProps) = {1, {PR_DISPLAY_NAME}};
  83. return Util::HrCopyPropTagArray(sptaDefaultProps, lppPropTagArray);
  84. }
  85. HRESULT DataCollector::GetRestriction(LPMAPIPROP lpProp, LPSRestriction *lppRestriction) {
  86. HRESULT hr = hrSuccess;
  87. SPropValue sPropOrphan;
  88. PROPMAP_START(1)
  89. PROPMAP_NAMED_ID(STORE_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, "store-entryids")
  90. PROPMAP_INIT(lpProp);
  91. sPropOrphan.ulPropTag = PR_EC_DELETED_STORE;
  92. sPropOrphan.Value.b = TRUE;
  93. hr = ECAndRestriction(
  94. ECNotRestriction(
  95. ECAndRestriction(
  96. ECExistRestriction(PR_EC_DELETED_STORE) +
  97. ECPropertyRestriction(RELOP_EQ, PR_EC_DELETED_STORE, &sPropOrphan, ECRestriction::Cheap)
  98. )
  99. ) +
  100. ECExistRestriction(CHANGE_PROP_TYPE(PROP_STORE_ENTRYIDS, PT_MV_BINARY))
  101. ).CreateMAPIRestriction(lppRestriction, ECRestriction::Full);
  102. exitpm:
  103. return hr;
  104. }
  105. HRESULT UserCountCollector::CollectData(LPMAPITABLE lpStoreTable) {
  106. ULONG ulCount = 0;
  107. HRESULT hr = lpStoreTable->GetRowCount(0, &ulCount);
  108. if (hr != hrSuccess)
  109. return hr;
  110. m_ulUserCount += ulCount;
  111. return hrSuccess;
  112. }
  113. inline unsigned int UserCountCollector::result() const {
  114. return m_ulUserCount;
  115. }
  116. template<typename string_type, ULONG prAccount>
  117. UserListCollector<string_type, prAccount>::UserListCollector(IMAPISession *lpSession): m_ptrSession(lpSession, true) {}
  118. template<typename string_type, ULONG prAccount>
  119. HRESULT UserListCollector<string_type, prAccount>::GetRequiredPropTags(LPMAPIPROP /*lpProp*/, LPSPropTagArray *lppPropTagArray) const {
  120. static constexpr const SizedSPropTagArray(1, sptaDefaultProps) =
  121. {1, {PR_MAILBOX_OWNER_ENTRYID}};
  122. return Util::HrCopyPropTagArray(sptaDefaultProps, lppPropTagArray);
  123. }
  124. template<typename string_type, ULONG prAccount>
  125. HRESULT UserListCollector<string_type, prAccount>::CollectData(LPMAPITABLE lpStoreTable) {
  126. while (true) {
  127. SRowSetPtr ptrRows;
  128. HRESULT hr = lpStoreTable->QueryRows(50, 0, &ptrRows);
  129. if (hr != hrSuccess)
  130. return hr;
  131. for (SRowSetPtr::size_type i = 0; i < ptrRows.size(); ++i) {
  132. if (ptrRows[i].lpProps[0].ulPropTag != PR_MAILBOX_OWNER_ENTRYID)
  133. continue;
  134. HRESULT hrTmp;
  135. ULONG ulType;
  136. MAPIPropPtr ptrUser;
  137. SPropValuePtr ptrAccount;
  138. hrTmp = m_ptrSession->OpenEntry(ptrRows[i].lpProps[0].Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrRows[i].lpProps[0].Value.bin.lpb), &ptrUser.iid(), 0, &ulType, &~ptrUser);
  139. if (hrTmp != hrSuccess)
  140. continue;
  141. hrTmp = HrGetOneProp(ptrUser, prAccount, &~ptrAccount);
  142. if (hrTmp != hrSuccess)
  143. continue;
  144. push_back(std::move(ptrAccount));
  145. }
  146. if (ptrRows.size() < 50)
  147. break;
  148. }
  149. return hrSuccess;
  150. }
  151. template<typename string_type, ULONG prAccount>
  152. void UserListCollector<string_type, prAccount>::swap_result(std::list<string_type> *lplstUsers) {
  153. lplstUsers->swap(m_lstUsers);
  154. }
  155. template<>
  156. void UserListCollector<std::string, PR_ACCOUNT_A>::push_back(LPSPropValue lpPropAccount) {
  157. m_lstUsers.push_back(lpPropAccount->Value.lpszA);
  158. }
  159. template<>
  160. void UserListCollector<std::wstring, PR_ACCOUNT_W>::push_back(LPSPropValue lpPropAccount) {
  161. m_lstUsers.push_back(lpPropAccount->Value.lpszW);
  162. }
  163. HRESULT GetArchivedUserList(IMAPISession *lpMapiSession, const char *lpSSLKey,
  164. const char *lpSSLPass, std::list<std::string> *lplstUsers, bool bLocalOnly)
  165. {
  166. UserListCollector<std::string, PR_ACCOUNT_A> collector(lpMapiSession);
  167. HRESULT hr = GetMailboxData(lpMapiSession, lpSSLKey, lpSSLPass,
  168. bLocalOnly, &collector);
  169. if (hr != hrSuccess)
  170. return hr;
  171. collector.swap_result(lplstUsers);
  172. return hrSuccess;
  173. }
  174. HRESULT GetArchivedUserList(IMAPISession *lpMapiSession, const char *lpSSLKey,
  175. const char *lpSSLPass, std::list<std::wstring> *lplstUsers, bool bLocalOnly)
  176. {
  177. UserListCollector<std::wstring, PR_ACCOUNT_W> collector(lpMapiSession);
  178. HRESULT hr = GetMailboxData(lpMapiSession, lpSSLKey, lpSSLPass,
  179. bLocalOnly, &collector);
  180. if (hr != hrSuccess)
  181. return hr;
  182. collector.swap_result(lplstUsers);
  183. return hrSuccess;
  184. }
  185. HRESULT GetMailboxData(IMAPISession *lpMapiSession, const char *lpSSLKey,
  186. const char *lpSSLPass, bool bLocalOnly, DataCollector *lpCollector)
  187. {
  188. HRESULT hr = S_OK;
  189. AddrBookPtr ptrAdrBook;
  190. EntryIdPtr ptrDDEntryID;
  191. ABContainerPtr ptrDefaultDir;
  192. ABContainerPtr ptrCompanyDir;
  193. MAPITablePtr ptrHierarchyTable;
  194. SRowSetPtr ptrRows;
  195. MsgStorePtr ptrStore;
  196. ECServiceAdminPtr ptrServiceAdmin;
  197. ULONG ulObj = 0;
  198. ULONG cbDDEntryID = 0;
  199. ULONG ulCompanyCount = 0;
  200. std::set<servername> listServers;
  201. convert_context converter;
  202. KCHL::memory_ptr<ECSVRNAMELIST> lpSrvNameList;
  203. KCHL::memory_ptr<ECSERVERLIST> lpSrvList;
  204. static constexpr const SizedSPropTagArray(1, sCols) = {1, {PR_ENTRYID}};
  205. if (lpMapiSession == nullptr || lpCollector == nullptr)
  206. return MAPI_E_INVALID_PARAMETER;
  207. hr = lpMapiSession->OpenAddressBook(0, &IID_IAddrBook, 0, &~ptrAdrBook);
  208. if(hr != hrSuccess) {
  209. ec_log_crit("Unable to open addressbook: 0x%08X", hr);
  210. return hr;
  211. }
  212. hr = ptrAdrBook->GetDefaultDir(&cbDDEntryID, &~ptrDDEntryID);
  213. if(hr != hrSuccess) {
  214. ec_log_crit("Unable to open default addressbook: 0x%08X", hr);
  215. return hr;
  216. }
  217. hr = ptrAdrBook->OpenEntry(cbDDEntryID, ptrDDEntryID, NULL, 0, &ulObj, &~ptrDefaultDir);
  218. if(hr != hrSuccess) {
  219. ec_log_crit("Unable to open GAB: 0x%08X", hr);
  220. return hr;
  221. }
  222. /* Open Hierarchy Table to see if we are running in multi-tenancy mode or not */
  223. hr = ptrDefaultDir->GetHierarchyTable(0, &~ptrHierarchyTable);
  224. if (hr != hrSuccess) {
  225. ec_log_crit("Unable to open hierarchy table: 0x%08X", hr);
  226. return hr;
  227. }
  228. hr = ptrHierarchyTable->GetRowCount(0, &ulCompanyCount);
  229. if(hr != hrSuccess) {
  230. ec_log_crit("Unable to get hierarchy row count: 0x%08X", hr);
  231. return hr;
  232. }
  233. if( ulCompanyCount > 0) {
  234. hr = ptrHierarchyTable->SetColumns(sCols, TBL_BATCH);
  235. if(hr != hrSuccess) {
  236. ec_log_crit("Unable to set set columns on user table: 0x%08X", hr);
  237. return hr;
  238. }
  239. /* multi-tenancy, loop through all subcontainers to find all users */
  240. hr = ptrHierarchyTable->QueryRows(ulCompanyCount, 0, &ptrRows);
  241. if (hr != hrSuccess)
  242. return hr;
  243. for (unsigned int i = 0; i < ptrRows.size(); ++i) {
  244. if (ptrRows[i].lpProps[0].ulPropTag != PR_ENTRYID) {
  245. ec_log_crit("Unable to get entryid to open tenancy Address Book");
  246. return MAPI_E_INVALID_PARAMETER;
  247. }
  248. hr = ptrAdrBook->OpenEntry(ptrRows[i].lpProps[0].Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrRows[i].lpProps[0].Value.bin.lpb), NULL, 0, &ulObj, &~ptrCompanyDir);
  249. if (hr != hrSuccess) {
  250. ec_log_crit("Unable to open tenancy Address Book: 0x%08X", hr);
  251. return hr;
  252. }
  253. hr = UpdateServerList(ptrCompanyDir, listServers);
  254. if(hr != hrSuccess) {
  255. ec_log_crit("Unable to create tenancy server list");
  256. return hr;
  257. }
  258. }
  259. } else {
  260. hr = UpdateServerList(ptrDefaultDir, listServers);
  261. if(hr != hrSuccess) {
  262. ec_log_crit("Unable to create server list");
  263. return hr;
  264. }
  265. }
  266. hr = HrOpenDefaultStore(lpMapiSession, &~ptrStore);
  267. if(hr != hrSuccess) {
  268. ec_log_crit("Unable to open default store: 0x%08X", hr);
  269. return hr;
  270. }
  271. //@todo use PT_OBJECT to queryinterface
  272. hr = ptrStore->QueryInterface(IID_IECServiceAdmin, &~ptrServiceAdmin);
  273. if (hr != hrSuccess)
  274. return hr;
  275. hr = MAPIAllocateBuffer(sizeof(ECSVRNAMELIST), &~lpSrvNameList);
  276. if (hr != hrSuccess)
  277. return hr;
  278. hr = MAPIAllocateMore(sizeof(WCHAR *) * listServers.size(), lpSrvNameList, (LPVOID *)&lpSrvNameList->lpszaServer);
  279. if (hr != hrSuccess)
  280. return hr;
  281. lpSrvNameList->cServers = 0;
  282. for (const auto &i : listServers)
  283. lpSrvNameList->lpszaServer[lpSrvNameList->cServers++] = i.c_str();
  284. hr = ptrServiceAdmin->GetServerDetails(lpSrvNameList, MAPI_UNICODE, &~lpSrvList);
  285. if (hr == MAPI_E_NETWORK_ERROR) {
  286. //support single server
  287. hr = GetMailboxDataPerServer(lpMapiSession, "", lpCollector);
  288. if (hr != hrSuccess)
  289. return hr;
  290. return hrSuccess;
  291. } else if (FAILED(hr)) {
  292. ec_log_err("Unable to get server details: 0x%08X", hr);
  293. if (hr == MAPI_E_NOT_FOUND) {
  294. ec_log_err("Details for one or more requested servers was not found.");
  295. ec_log_err("This usually indicates a misconfigured home server for a user.");
  296. ec_log_err("Requested servers:");
  297. for (const auto &i : listServers)
  298. ec_log_err("* %ls", i.c_str());
  299. }
  300. return hr;
  301. }
  302. for (ULONG i = 0; i < lpSrvList->cServers; ++i) {
  303. wchar_t *wszPath = NULL;
  304. ec_log_info("Check server: \"%ls\" ssl=\"%ls\" flag=%08x",
  305. (lpSrvList->lpsaServer[i].lpszName)?lpSrvList->lpsaServer[i].lpszName : L"<UNKNOWN>",
  306. (lpSrvList->lpsaServer[i].lpszSslPath)?lpSrvList->lpsaServer[i].lpszSslPath : L"<UNKNOWN>",
  307. lpSrvList->lpsaServer[i].ulFlags);
  308. if (bLocalOnly && (lpSrvList->lpsaServer[i].ulFlags & EC_SDFLAG_IS_PEER) == 0) {
  309. ec_log_info("Skipping remote server: \"%ls\".",
  310. (lpSrvList->lpsaServer[i].lpszName)?lpSrvList->lpsaServer[i].lpszName : L"<UNKNOWN>");
  311. continue;
  312. }
  313. if (lpSrvList->lpsaServer[i].ulFlags & EC_SDFLAG_IS_PEER &&
  314. lpSrvList->lpsaServer[i].lpszFilePath != nullptr)
  315. wszPath = lpSrvList->lpsaServer[i].lpszFilePath;
  316. if (wszPath == NULL) {
  317. if(lpSrvList->lpsaServer[i].lpszSslPath == NULL) {
  318. ec_log_err("No SSL or File path found for server: \"%ls\", please fix your configuration.", lpSrvList->lpsaServer[i].lpszName);
  319. return hr;
  320. }
  321. wszPath = lpSrvList->lpsaServer[i].lpszSslPath;
  322. }
  323. hr = GetMailboxDataPerServer(converter.convert_to<char *>(wszPath), lpSSLKey, lpSSLPass, lpCollector);
  324. if(FAILED(hr)) {
  325. ec_log_err("Failed to collect data from server: \"%ls\", hr: 0x%08x", wszPath, hr);
  326. return hr;
  327. }
  328. }
  329. return hrSuccess;
  330. }
  331. HRESULT GetMailboxDataPerServer(const char *lpszPath, const char *lpSSLKey,
  332. const char *lpSSLPass, DataCollector *lpCollector)
  333. {
  334. MAPISessionPtr ptrSessionServer;
  335. HRESULT hr = HrOpenECAdminSession(&~ptrSessionServer, "userutil.cpp",
  336. "GetMailboxDataPerServer", lpszPath, 0, lpSSLKey,
  337. lpSSLPass);
  338. if(hr != hrSuccess) {
  339. ec_log_crit("Unable to open admin session on server \"%s\": 0x%08X", lpszPath, hr);
  340. return hr;
  341. }
  342. return GetMailboxDataPerServer(ptrSessionServer, lpszPath, lpCollector);
  343. }
  344. /**
  345. * Get archived user count per server
  346. *
  347. * @param[in] lpszPath Path to a server
  348. * @param[out] lpulArchivedUsers The amount of archived user on the give server
  349. *
  350. * @return Mapi errors
  351. */
  352. HRESULT GetMailboxDataPerServer(IMAPISession *lpSession, const char *lpszPath,
  353. DataCollector *lpCollector)
  354. {
  355. MsgStorePtr ptrStoreAdmin;
  356. MAPITablePtr ptrStoreTable;
  357. SPropTagArrayPtr ptrPropTagArray;
  358. SRestrictionPtr ptrRestriction;
  359. ExchangeManageStorePtr ptrEMS;
  360. HRESULT hr = HrOpenDefaultStore(lpSession, &~ptrStoreAdmin);
  361. if(hr != hrSuccess) {
  362. ec_log_crit("Unable to open default store on server \"%s\": 0x%08X", lpszPath, hr);
  363. return hr;
  364. }
  365. //@todo use PT_OBJECT to queryinterface
  366. hr = ptrStoreAdmin->QueryInterface(IID_IExchangeManageStore, &~ptrEMS);
  367. if (hr != hrSuccess)
  368. return hr;
  369. hr = ptrEMS->GetMailboxTable(nullptr, &~ptrStoreTable, MAPI_DEFERRED_ERRORS);
  370. if (hr != hrSuccess)
  371. return hr;
  372. hr = lpCollector->GetRequiredPropTags(ptrStoreAdmin, &~ptrPropTagArray);
  373. if (hr != hrSuccess)
  374. return hr;
  375. hr = ptrStoreTable->SetColumns(ptrPropTagArray, TBL_BATCH);
  376. if (hr != hrSuccess)
  377. return hr;
  378. hr = lpCollector->GetRestriction(ptrStoreAdmin, &~ptrRestriction);
  379. if (hr != hrSuccess)
  380. return hr;
  381. hr = ptrStoreTable->Restrict(ptrRestriction, TBL_BATCH);
  382. if (hr != hrSuccess)
  383. return hr;
  384. return lpCollector->CollectData(ptrStoreTable);
  385. }
  386. /**
  387. * Build a server list from a countainer with users
  388. *
  389. * @param[in] lpContainer A container to get users, groups and other objects
  390. * @param[in,out] A set with server names. The new servers will be added
  391. *
  392. * @return MAPI error codes
  393. */
  394. HRESULT UpdateServerList(IABContainer *lpContainer,
  395. std::set<servername> &listServers)
  396. {
  397. SRowSetPtr ptrRows;
  398. MAPITablePtr ptrTable;
  399. SPropValue sPropUser;
  400. SPropValue sPropDisplayType;
  401. static constexpr const SizedSPropTagArray(2, sCols) =
  402. {2, {PR_EC_HOMESERVER_NAME_W, PR_DISPLAY_NAME_W}};
  403. sPropDisplayType.ulPropTag = PR_DISPLAY_TYPE;
  404. sPropDisplayType.Value.ul = DT_REMOTE_MAILUSER;
  405. sPropUser.ulPropTag = PR_OBJECT_TYPE;
  406. sPropUser.Value.ul = MAPI_MAILUSER;
  407. HRESULT hr = lpContainer->GetContentsTable(MAPI_DEFERRED_ERRORS, &~ptrTable);
  408. if(hr != hrSuccess) {
  409. ec_log_crit("Unable to open contents table: 0x%08X", hr);
  410. return hr;
  411. }
  412. hr = ptrTable->SetColumns(sCols, TBL_BATCH);
  413. if(hr != hrSuccess) {
  414. ec_log_crit("Unable to set set columns on user table: 0x%08X", hr);
  415. return hr;
  416. }
  417. // Restrict to users (not groups)
  418. hr = ECAndRestriction(
  419. ECPropertyRestriction(RELOP_NE, PR_DISPLAY_TYPE, &sPropDisplayType, ECRestriction::Cheap) +
  420. ECPropertyRestriction(RELOP_EQ, PR_OBJECT_TYPE, &sPropUser, ECRestriction::Cheap))
  421. .RestrictTable(ptrTable, TBL_BATCH);
  422. if (hr != hrSuccess) {
  423. ec_log_crit("Unable to get total user count: 0x%08X", hr);
  424. return hr;
  425. }
  426. while (true) {
  427. hr = ptrTable->QueryRows(50, 0, &ptrRows);
  428. if (hr != hrSuccess)
  429. return hr;
  430. if (ptrRows.empty())
  431. break;
  432. for (unsigned int i = 0; i < ptrRows.size(); ++i) {
  433. if (ptrRows[i].lpProps[0].ulPropTag != PR_EC_HOMESERVER_NAME_W)
  434. continue;
  435. listServers.insert(ptrRows[i].lpProps[0].Value.lpszW);
  436. if (ptrRows[i].lpProps[1].ulPropTag == PR_DISPLAY_NAME_W)
  437. ec_log_info("User: %ls on server \"%ls\"", ptrRows[i].lpProps[1].Value.lpszW, ptrRows[i].lpProps[0].Value.lpszW);
  438. }
  439. }
  440. return hrSuccess;
  441. }
  442. } /* namespace */