ECQuotaMonitor.cpp 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240
  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 <iterator>
  19. #include <memory>
  20. #include <string>
  21. #include <utility>
  22. #include <vector>
  23. #include <kopano/memory.hpp>
  24. // Mapi includes
  25. #include <mapi.h>
  26. #include <mapix.h>
  27. #include <mapiutil.h>
  28. #include <mapidefs.h>
  29. #include <edkguid.h>
  30. #include <edkmdb.h>
  31. // Kopano includes
  32. #include <kopano/ECDefs.h>
  33. #include <kopano/ECRestriction.h>
  34. #include <kopano/ECABEntryID.h>
  35. #include <kopano/IECUnknown.h>
  36. #include <kopano/Util.h>
  37. #include <kopano/ecversion.h>
  38. #include <kopano/charset/convert.h>
  39. #include <kopano/mapi_ptr.h>
  40. #include <kopano/MAPIErrors.h>
  41. //#include <kopano/IECSecurity.h>
  42. #include <kopano/ECGuid.h>
  43. #include <kopano/ECTags.h>
  44. #include <kopano/IECServiceAdmin.h>
  45. #include <kopano/CommonUtil.h>
  46. #include <kopano/stringutil.h>
  47. #include <kopano/mapiext.h>
  48. // Other
  49. #include "ECMonitorDefs.h"
  50. #include "ECQuotaMonitor.h"
  51. #include <set>
  52. #include <string>
  53. using namespace std;
  54. using namespace KCHL;
  55. #define QUOTA_CONFIG_MSG "Kopano.Quota"
  56. /**
  57. * Takes an extra reference to the passed MAPI objects which have refcounting.
  58. */
  59. ECQuotaMonitor::ECQuotaMonitor(ECTHREADMONITOR *lpThreadMonitor,
  60. LPMAPISESSION lpMAPIAdminSession, LPMDB lpMDBAdmin) :
  61. m_lpThreadMonitor(lpThreadMonitor),
  62. m_lpMAPIAdminSession(lpMAPIAdminSession), m_lpMDBAdmin(lpMDBAdmin)
  63. {
  64. if(lpMAPIAdminSession)
  65. lpMAPIAdminSession->AddRef();
  66. if(lpMDBAdmin)
  67. lpMDBAdmin->AddRef();
  68. }
  69. /**
  70. * Releases references to passed MAPI objects.
  71. */
  72. ECQuotaMonitor::~ECQuotaMonitor()
  73. {
  74. if(m_lpMDBAdmin)
  75. m_lpMDBAdmin->Release();
  76. if(m_lpMAPIAdminSession)
  77. m_lpMAPIAdminSession->Release();
  78. }
  79. /** Creates ECQuotaMonitor object and calls
  80. * ECQuotaMonitor::CheckQuota(). Entry point for this class.
  81. *
  82. * @param[in] lpVoid ECTHREADMONITOR struct
  83. * @return NULL
  84. */
  85. void* ECQuotaMonitor::Create(void* lpVoid)
  86. {
  87. HRESULT hr = hrSuccess;
  88. auto lpThreadMonitor = static_cast<ECTHREADMONITOR *>(lpVoid);
  89. std::unique_ptr<ECQuotaMonitor> lpecQuotaMonitor;
  90. object_ptr<IMAPISession> lpMAPIAdminSession;
  91. object_ptr<IMsgStore> lpMDBAdmin;
  92. time_t tmStart = 0;
  93. time_t tmEnd = 0;
  94. const char *lpPath = lpThreadMonitor->lpConfig->GetSetting("server_socket");
  95. lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_INFO, "Quota monitor starting");
  96. //Open admin session
  97. hr = HrOpenECAdminSession(&~lpMAPIAdminSession, "kopano-monitor:create",
  98. PROJECT_SVN_REV_STR, lpPath, 0,
  99. lpThreadMonitor->lpConfig->GetSetting("sslkey_file", "", NULL),
  100. lpThreadMonitor->lpConfig->GetSetting("sslkey_pass", "", NULL));
  101. if (hr != hrSuccess) {
  102. lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to open an admin session. Error 0x%X", hr);
  103. return NULL;
  104. }
  105. lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_INFO, "Connection to storage server succeeded");
  106. // Open admin store
  107. hr = HrOpenDefaultStore(lpMAPIAdminSession, &~lpMDBAdmin);
  108. if (hr != hrSuccess) {
  109. lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to open default store for system account");
  110. return NULL;
  111. }
  112. lpecQuotaMonitor.reset(new ECQuotaMonitor(lpThreadMonitor, lpMAPIAdminSession, lpMDBAdmin));
  113. // Check the quota of all stores
  114. tmStart = GetProcessTime();
  115. hr = lpecQuotaMonitor->CheckQuota();
  116. tmEnd = GetProcessTime();
  117. if(hr != hrSuccess)
  118. lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Quota monitor failed");
  119. else
  120. lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_INFO, "Quota monitor done in %lu seconds. Processed: %u, Failed: %u", tmEnd - tmStart, lpecQuotaMonitor->m_ulProcessed, lpecQuotaMonitor->m_ulFailed);
  121. return NULL;
  122. }
  123. /** Gets a list of companies and checks the quota. Then it calls
  124. * ECQuotaMonitor::CheckCompanyQuota() to check quota of the users
  125. * in the company. If the server is not running in hosted mode,
  126. * the default company 0 will be used.
  127. *
  128. * @return hrSuccess or any MAPI error code.
  129. */
  130. HRESULT ECQuotaMonitor::CheckQuota()
  131. {
  132. HRESULT hr = hrSuccess;
  133. /* Service object */
  134. object_ptr<IECServiceAdmin> lpServiceAdmin;
  135. memory_ptr<SPropValue> lpsObject;
  136. object_ptr<IExchangeManageStore> lpIEMS;
  137. /* Companylist */
  138. ECCOMPANY *lpsCompanyList = NULL;
  139. memory_ptr<ECCOMPANY> lpsCompanyListAlloc;
  140. ECCOMPANY sRootCompany = {{g_cbSystemEid, g_lpSystemEid}, (LPTSTR)"Default", NULL, {0, NULL}};
  141. ULONG cCompanies = 0;
  142. /* Quota information */
  143. memory_ptr<ECQUOTA> lpsQuota;
  144. memory_ptr<ECQUOTASTATUS> lpsQuotaStatus;
  145. /* Obtain Service object */
  146. hr = HrGetOneProp(m_lpMDBAdmin, PR_EC_OBJECT, &~lpsObject);
  147. if(hr != hrSuccess) {
  148. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get internal object, error code: 0x%08X", hr);
  149. return hr;
  150. }
  151. hr = reinterpret_cast<IECUnknown *>(lpsObject->Value.lpszA)->QueryInterface(IID_IECServiceAdmin, &~lpServiceAdmin);
  152. if(hr != hrSuccess) {
  153. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get service admin, error code: 0x%08X", hr);
  154. return hr;
  155. }
  156. /* Get companylist */
  157. hr = lpServiceAdmin->GetCompanyList(0, &cCompanies, &~lpsCompanyListAlloc);
  158. if (hr == MAPI_E_NO_SUPPORT) {
  159. lpsCompanyList = &sRootCompany;
  160. cCompanies = 1;
  161. hr = hrSuccess;
  162. } else if (hr != hrSuccess) {
  163. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get companylist, error code 0x%08X", hr);
  164. return hr;
  165. } else
  166. lpsCompanyList = lpsCompanyListAlloc;
  167. hr = m_lpMDBAdmin->QueryInterface(IID_IExchangeManageStore, &~lpIEMS);
  168. if (hr != hrSuccess) {
  169. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get admin interface, error code 0x%08X", hr);
  170. return hr;
  171. }
  172. for (ULONG i = 0; i < cCompanies; ++i) {
  173. /* Check company quota for non-default company */
  174. if (lpsCompanyList[i].sCompanyId.cb != 0 && lpsCompanyList[i].sCompanyId.lpb != NULL) {
  175. /* Company store */
  176. object_ptr<IMsgStore> lpMsgStore;
  177. ++m_ulProcessed;
  178. hr = lpServiceAdmin->GetQuota(lpsCompanyList[i].sCompanyId.cb, (LPENTRYID)lpsCompanyList[i].sCompanyId.lpb, false, &~lpsQuota);
  179. if (hr != hrSuccess) {
  180. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get quota information for company %s, error code: 0x%08X", (LPSTR)lpsCompanyList[i].lpszCompanyname, hr);
  181. ++m_ulFailed;
  182. goto check_stores;
  183. }
  184. hr = OpenUserStore(lpsCompanyList[i].lpszCompanyname, CONTAINER_COMPANY, &~lpMsgStore);
  185. if (hr != hrSuccess) {
  186. ++m_ulFailed;
  187. goto check_stores;
  188. }
  189. hr = Util::HrGetQuotaStatus(lpMsgStore, lpsQuota, &~lpsQuotaStatus);
  190. if (hr != hrSuccess) {
  191. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get quotastatus for company %s, error code: 0x%08X", (LPSTR)lpsCompanyList[i].lpszCompanyname, hr);
  192. ++m_ulFailed;
  193. goto check_stores;
  194. }
  195. if (lpsQuotaStatus->quotaStatus != QUOTA_OK) {
  196. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Storage size of company %s has exceeded one or more size limits", (LPSTR)lpsCompanyList[i].lpszCompanyname);
  197. Notify(NULL, &lpsCompanyList[i], lpsQuotaStatus, lpMsgStore);
  198. }
  199. }
  200. check_stores:
  201. /* Whatever the status of the company quota, we should also check the quota of the users */
  202. CheckCompanyQuota(&lpsCompanyList[i]);
  203. }
  204. return hrSuccess;
  205. }
  206. /** Uses the ECServiceAdmin to get a list of all users within a
  207. * given company and groups those per kopano-server instance. Per
  208. * server it calls ECQuotaMonitor::CheckServerQuota().
  209. *
  210. * @param[in] company lpecCompany ECCompany struct
  211. * @return hrSuccess or any MAPI error code.
  212. */
  213. HRESULT ECQuotaMonitor::CheckCompanyQuota(ECCOMPANY *lpecCompany)
  214. {
  215. HRESULT hr = hrSuccess;
  216. /* Service object */
  217. object_ptr<IECServiceAdmin> lpServiceAdmin;
  218. memory_ptr<SPropValue> lpsObject;
  219. /* Userlist */
  220. memory_ptr<ECUSER> lpsUserList;
  221. ULONG cUsers = 0;
  222. set<string> setServers;
  223. const char *lpszServersConfig;
  224. std::set<string, strcasecmp_comparison> setServersConfig;
  225. memory_ptr<char> lpszConnection;
  226. bool bIsPeer = false;
  227. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_INFO, "Checking quota for company %s", (char*)lpecCompany->lpszCompanyname);
  228. /* Obtain Service object */
  229. hr = HrGetOneProp(m_lpMDBAdmin, PR_EC_OBJECT, &~lpsObject);
  230. if(hr != hrSuccess) {
  231. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get internal object, error code: 0x%08X", hr);
  232. return hr;
  233. }
  234. hr = reinterpret_cast<IECUnknown *>(lpsObject->Value.lpszA)->QueryInterface(IID_IECServiceAdmin, &~lpServiceAdmin);
  235. if(hr != hrSuccess) {
  236. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get service admin, error code: 0x%08X", hr);
  237. return hr;
  238. }
  239. /* Get userlist */
  240. hr = lpServiceAdmin->GetUserList(lpecCompany->sCompanyId.cb, (LPENTRYID)lpecCompany->sCompanyId.lpb, 0, &cUsers, &~lpsUserList);
  241. if (hr != hrSuccess) {
  242. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get userlist for company %s, error code 0x%08X", (LPSTR)lpecCompany->lpszCompanyname, hr);
  243. return hr;
  244. }
  245. for (ULONG i = 0; i < cUsers; ++i)
  246. if (lpsUserList[i].lpszServername && lpsUserList[i].lpszServername[0] != '\0')
  247. setServers.insert((char*)lpsUserList[i].lpszServername);
  248. if (setServers.empty()) {
  249. // call server function with current lpMDBAdmin / lpServiceAdmin
  250. hr = CheckServerQuota(cUsers, lpsUserList, lpecCompany, m_lpMDBAdmin);
  251. if (hr != hrSuccess) {
  252. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to check server quota, error code 0x%08X", hr);
  253. return hr;
  254. }
  255. return hrSuccess;
  256. }
  257. lpszServersConfig = m_lpThreadMonitor->lpConfig->GetSetting("servers","",NULL);
  258. if(lpszServersConfig) {
  259. // split approach taken from kopano-backup/backup.cpp
  260. std::vector<std::string> ddv = tokenize(lpszServersConfig, "\t ");
  261. std::move(ddv.begin(), ddv.end(), std::inserter(setServersConfig, setServersConfig.begin()));
  262. }
  263. for (const auto &server : setServers) {
  264. if (!setServersConfig.empty() &&
  265. setServersConfig.find(server.c_str()) == setServersConfig.cend())
  266. continue;
  267. hr = lpServiceAdmin->ResolvePseudoUrl(std::string("pseudo://" + server).c_str(), &~lpszConnection, &bIsPeer);
  268. if (hr != hrSuccess) {
  269. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to resolve servername %s, error code 0x%08X", server.c_str(), hr);
  270. ++m_ulFailed;
  271. continue;
  272. }
  273. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_INFO, "Connecting to server %s using url %s", server.c_str(), lpszConnection.get());
  274. // call server function with new lpMDBAdmin / lpServiceAdmin
  275. /* 2nd Server connection */
  276. object_ptr<IMAPISession> lpSession;
  277. object_ptr<IMsgStore> lpAdminStore;
  278. if (bIsPeer) {
  279. // query interface
  280. hr = m_lpMDBAdmin->QueryInterface(IID_IMsgStore, &~lpAdminStore);
  281. if (hr != hrSuccess) {
  282. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get service interface again, error code 0x%08X", hr);
  283. ++m_ulFailed;
  284. continue;
  285. }
  286. } else {
  287. hr = HrOpenECAdminSession(&~lpSession, "kopano-monitor:check-company", PROJECT_SVN_REV_STR, lpszConnection, 0, m_lpThreadMonitor->lpConfig->GetSetting("sslkey_file", "", nullptr), m_lpThreadMonitor->lpConfig->GetSetting("sslkey_pass", "", nullptr));
  288. if (hr != hrSuccess) {
  289. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to connect to server %s, error code 0x%08X", lpszConnection.get(), hr);
  290. ++m_ulFailed;
  291. continue;
  292. }
  293. hr = HrOpenDefaultStore(lpSession, &~lpAdminStore);
  294. if (hr != hrSuccess) {
  295. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to open admin store on server %s, error code 0x%08X", lpszConnection.get(), hr);
  296. ++m_ulFailed;
  297. continue;
  298. }
  299. }
  300. hr = CheckServerQuota(cUsers, lpsUserList, lpecCompany, lpAdminStore);
  301. if (hr != hrSuccess) {
  302. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to check quota on server %s, error code 0x%08X", lpszConnection.get(), hr);
  303. ++m_ulFailed;
  304. }
  305. }
  306. return hrSuccess;
  307. }
  308. /**
  309. * Checks in the ECStatsTable PR_EC_STATSTABLE_USERS for quota
  310. * information per connected server given in lpAdminStore.
  311. *
  312. * @param[in] cUsers number of users in lpsUserList
  313. * @param[in] lpsUserList array of ECUser struct, containing all Kopano from all companies, on any server
  314. * @param[in] lpecCompany same company struct as in ECQuotaMonitor::CheckCompanyQuota()
  315. * @param[in] lpAdminStore IMsgStore of SYSTEM user on a specific server instance.
  316. * @return hrSuccess or any MAPI error code.
  317. */
  318. HRESULT ECQuotaMonitor::CheckServerQuota(ULONG cUsers, ECUSER *lpsUserList,
  319. ECCOMPANY *lpecCompany, LPMDB lpAdminStore)
  320. {
  321. HRESULT hr = hrSuccess;
  322. SPropValue sRestrictProp;
  323. object_ptr<IMAPITable> lpTable;
  324. ECQUOTASTATUS sQuotaStatus;
  325. ULONG i, u;
  326. static constexpr const SizedSPropTagArray(5, sCols) =
  327. {5, {PR_EC_USERNAME_A, PR_MESSAGE_SIZE_EXTENDED,
  328. PR_QUOTA_WARNING_THRESHOLD, PR_QUOTA_SEND_THRESHOLD,
  329. PR_QUOTA_RECEIVE_THRESHOLD}};
  330. hr = lpAdminStore->OpenProperty(PR_EC_STATSTABLE_USERS, &IID_IMAPITable, 0, 0, &~lpTable);
  331. if (hr != hrSuccess) {
  332. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to open stats table for quota sizes, error 0x%08X", hr);
  333. return hr;
  334. }
  335. hr = lpTable->SetColumns(sCols, MAPI_DEFERRED_ERRORS);
  336. if (hr != hrSuccess) {
  337. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to set columns on stats table for quota sizes, error 0x%08X", hr);
  338. return hr;
  339. }
  340. if (lpecCompany->sCompanyId.cb != 0 && lpecCompany->sCompanyId.lpb != NULL) {
  341. sRestrictProp.ulPropTag = PR_EC_COMPANY_NAME_A;
  342. sRestrictProp.Value.lpszA = (char*)lpecCompany->lpszCompanyname;
  343. memory_ptr<SRestriction> lpsRestriction;
  344. hr = ECOrRestriction(
  345. ECNotRestriction(ECExistRestriction(PR_EC_COMPANY_NAME_A)) +
  346. ECPropertyRestriction(RELOP_EQ, PR_EC_COMPANY_NAME_A, &sRestrictProp, ECRestriction::Cheap)
  347. ).CreateMAPIRestriction(&~lpsRestriction, ECRestriction::Cheap);
  348. if (hr != hrSuccess)
  349. return hr;
  350. hr = lpTable->Restrict(lpsRestriction, MAPI_DEFERRED_ERRORS);
  351. if (hr != hrSuccess) {
  352. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to restrict stats table, error 0x%08X", hr);
  353. return hr;
  354. }
  355. }
  356. while (TRUE) {
  357. rowset_ptr lpRowSet;
  358. hr = lpTable->QueryRows(50, 0, &~lpRowSet);
  359. if (hr != hrSuccess) {
  360. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to receive stats table data, error 0x%08X", hr);
  361. return hr;
  362. }
  363. if (lpRowSet->cRows == 0)
  364. break;
  365. for (i = 0; i < lpRowSet->cRows; ++i) {
  366. MsgStorePtr ptrStore;
  367. auto lpUsername = PCpropFindProp(lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_EC_USERNAME_A);
  368. auto lpStoreSize = PCpropFindProp(lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_MESSAGE_SIZE_EXTENDED);
  369. auto lpQuotaWarn = PCpropFindProp(lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_QUOTA_WARNING_THRESHOLD);
  370. auto lpQuotaSoft = PCpropFindProp(lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_QUOTA_SEND_THRESHOLD);
  371. auto lpQuotaHard = PCpropFindProp(lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_QUOTA_RECEIVE_THRESHOLD);
  372. if (!lpUsername || !lpStoreSize)
  373. continue; // don't log error: could be for several valid reasons (contacts, other server, etc)
  374. if (lpStoreSize->Value.li.QuadPart == 0)
  375. continue;
  376. ++m_ulProcessed;
  377. memset(&sQuotaStatus, 0, sizeof(ECQUOTASTATUS));
  378. sQuotaStatus.llStoreSize = lpStoreSize->Value.li.QuadPart;
  379. sQuotaStatus.quotaStatus = QUOTA_OK;
  380. if (lpQuotaHard && lpQuotaHard->Value.ul > 0 && lpStoreSize->Value.li.QuadPart > ((long long)lpQuotaHard->Value.ul * 1024))
  381. sQuotaStatus.quotaStatus = QUOTA_HARDLIMIT;
  382. else if (lpQuotaSoft && lpQuotaSoft->Value.ul > 0 && lpStoreSize->Value.li.QuadPart > ((long long)lpQuotaSoft->Value.ul * 1024))
  383. sQuotaStatus.quotaStatus = QUOTA_SOFTLIMIT;
  384. else if (lpQuotaWarn && lpQuotaWarn->Value.ul > 0 && lpStoreSize->Value.li.QuadPart > ((long long)lpQuotaWarn->Value.ul * 1024))
  385. sQuotaStatus.quotaStatus = QUOTA_WARN;
  386. if (sQuotaStatus.quotaStatus == QUOTA_OK)
  387. continue;
  388. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Mailbox of user %s has exceeded its %s limit", lpUsername->Value.lpszA, sQuotaStatus.quotaStatus == QUOTA_WARN ? "warning" : sQuotaStatus.quotaStatus == QUOTA_SOFTLIMIT ? "soft" : "hard");
  389. // find the user in the full users list
  390. for (u = 0; u < cUsers; ++u) {
  391. if (strcmp((char*)lpsUserList[u].lpszUsername, lpUsername->Value.lpszA) == 0)
  392. break;
  393. }
  394. if (u == cUsers) {
  395. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to find user %s in userlist", lpUsername->Value.lpszA);
  396. ++m_ulFailed;
  397. continue;
  398. }
  399. hr = OpenUserStore(lpsUserList[u].lpszUsername, ACTIVE_USER, &~ptrStore);
  400. if (hr != hrSuccess) {
  401. hr = hrSuccess;
  402. continue;
  403. }
  404. hr = Notify(&lpsUserList[u], lpecCompany, &sQuotaStatus, ptrStore);
  405. if (hr != hrSuccess)
  406. ++m_ulFailed;
  407. }
  408. }
  409. return hrSuccess;
  410. }
  411. /**
  412. * Returns an email body and subject string with template variables replaced.
  413. *
  414. * @param[in] lpVars structure with all template variable strings
  415. * @param[out] lpstrSubject the filled in subject string
  416. * @param[out] lpstrBody the filled in mail body
  417. * @retval MAPI_E_NOT_FOUND the template file set in the config was not found
  418. */
  419. HRESULT ECQuotaMonitor::CreateMailFromTemplate(TemplateVariables *lpVars, string *lpstrSubject, string *lpstrBody)
  420. {
  421. string strTemplateConfig;
  422. const char *lpszTemplate = NULL;
  423. FILE *fp = NULL;
  424. char cBuffer[TEMPLATE_LINE_LENGTH];
  425. string strLine;
  426. string strSubject;
  427. string strBody;
  428. size_t pos;
  429. string strVariables[7][2] = {
  430. { "${KOPANO_QUOTA_NAME}", "unknown" },
  431. { "${KOPANO_QUOTA_FULLNAME}" , "unknown" },
  432. { "${KOPANO_QUOTA_COMPANY}", "unknown" },
  433. { "${KOPANO_QUOTA_STORE_SIZE}", "unknown" },
  434. { "${KOPANO_QUOTA_WARN_SIZE}", "unlimited" },
  435. { "${KOPANO_QUOTA_SOFT_SIZE}", "unlimited" },
  436. { "${KOPANO_QUOTA_HARD_SIZE}", "unlimited" },
  437. };
  438. enum enumVariables {
  439. KOPANO_QUOTA_NAME,
  440. KOPANO_QUOTA_FULLNAME,
  441. KOPANO_QUOTA_COMPANY,
  442. KOPANO_QUOTA_STORE_SIZE,
  443. KOPANO_QUOTA_WARN_SIZE,
  444. KOPANO_QUOTA_SOFT_SIZE,
  445. KOPANO_QUOTA_HARD_SIZE,
  446. KOPANO_QUOTA_LAST_ITEM /* KEEP LAST! */
  447. };
  448. if (lpVars->ulClass == CONTAINER_COMPANY) {
  449. // Company public stores only support QUOTA_WARN.
  450. strTemplateConfig = "companyquota_warning_template";
  451. } else {
  452. switch (lpVars->ulStatus) {
  453. case QUOTA_WARN:
  454. strTemplateConfig = "userquota_warning_template";
  455. break;
  456. case QUOTA_SOFTLIMIT:
  457. strTemplateConfig = "userquota_soft_template";
  458. break;
  459. case QUOTA_HARDLIMIT:
  460. strTemplateConfig = "userquota_warning_template";
  461. break;
  462. case QUOTA_OK:
  463. default:
  464. return hrSuccess;
  465. }
  466. }
  467. lpszTemplate = m_lpThreadMonitor->lpConfig->GetSetting(strTemplateConfig.c_str());
  468. /* Start reading the template mail */
  469. if((fp = fopen(lpszTemplate, "rt")) == NULL) {
  470. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to open template email: %s", lpszTemplate);
  471. return MAPI_E_NOT_FOUND;
  472. }
  473. while(!feof(fp)) {
  474. memset(&cBuffer, 0, sizeof(cBuffer));
  475. if (!fgets(cBuffer, sizeof(cBuffer), fp))
  476. break;
  477. strLine = string(cBuffer);
  478. /* If this is the subject line, don't attach it to the mail */
  479. if (strLine.compare(0, strlen("Subject:"), "Subject:") == 0)
  480. strSubject = strLine.substr(strLine.find_first_not_of(" ", strlen("Subject:")));
  481. else
  482. strBody += strLine;
  483. }
  484. fclose(fp);
  485. fp = NULL;
  486. if (!lpVars->strUserName.empty())
  487. strVariables[KOPANO_QUOTA_NAME][1] = lpVars->strUserName;
  488. if (!lpVars->strFullName.empty())
  489. strVariables[KOPANO_QUOTA_FULLNAME][1] = lpVars->strFullName;
  490. if (!lpVars->strCompany.empty())
  491. strVariables[KOPANO_QUOTA_COMPANY][1] = lpVars->strCompany;
  492. if (!lpVars->strStoreSize.empty())
  493. strVariables[KOPANO_QUOTA_STORE_SIZE][1] = lpVars->strStoreSize;
  494. if (!lpVars->strWarnSize.empty())
  495. strVariables[KOPANO_QUOTA_WARN_SIZE][1] = lpVars->strWarnSize;
  496. if (!lpVars->strSoftSize.empty())
  497. strVariables[KOPANO_QUOTA_SOFT_SIZE][1] = lpVars->strSoftSize;
  498. if (!lpVars->strHardSize.empty())
  499. strVariables[KOPANO_QUOTA_HARD_SIZE][1] = lpVars->strHardSize;
  500. for (unsigned int i = 0; i < KOPANO_QUOTA_LAST_ITEM; ++i) {
  501. pos = 0;
  502. while ((pos = strSubject.find(strVariables[i][0], pos)) != string::npos) {
  503. strSubject.replace(pos, strVariables[i][0].size(), strVariables[i][1]);
  504. pos += strVariables[i][1].size();
  505. }
  506. pos = 0;
  507. while ((pos = strBody.find(strVariables[i][0], pos)) != string::npos) {
  508. strBody.replace(pos, strVariables[i][0].size(), strVariables[i][1]);
  509. pos += strVariables[i][1].size();
  510. }
  511. }
  512. /* Clear end-of-line characters from subject */
  513. pos = strSubject.find('\n');
  514. if (pos != string::npos)
  515. strSubject.erase(pos);
  516. pos = strSubject.find('\r');
  517. if (pos != string::npos)
  518. strSubject.erase(pos);
  519. /* Clear starting blank lines from body */
  520. while (strBody[0] == '\r' || strBody[0] == '\n')
  521. strBody.erase(0, 1);
  522. *lpstrSubject = std::move(strSubject);
  523. *lpstrBody = std::move(strBody);
  524. return hrSuccess;
  525. }
  526. /**
  527. * Creates a set of properties which need to be set on an IMessage
  528. * which becomes the MAPI quota message.
  529. *
  530. * @param[in] lpecToUser User which is set in the received properties
  531. * @param[in] lpecFromUser User which is set in the sender properties
  532. * @param[in] strSubject The subject field
  533. * @param[in] strBody The plain text body
  534. * @param[out] lpcPropSize The number of properties in lppPropArray
  535. * @param[out] lppPropArray The properties to write to the IMessage
  536. * @retval MAPI_E_NOT_ENOUGH_MEMORY unable to allocate more memory
  537. */
  538. HRESULT ECQuotaMonitor::CreateMessageProperties(ECUSER *lpecToUser,
  539. ECUSER *lpecFromUser, const std::string &strSubject,
  540. const std::string &strBody, ULONG *lpcPropSize, LPSPropValue *lppPropArray)
  541. {
  542. HRESULT hr = hrSuccess;
  543. memory_ptr<SPropValue> lpPropArray;
  544. ULONG cbFromEntryid = 0;
  545. memory_ptr<ENTRYID> lpFromEntryid, lpToEntryid;
  546. ULONG cbToEntryid = 0;
  547. ULONG cbFromSearchKey = 0;
  548. memory_ptr<unsigned char> lpFromSearchKey, lpToSearchKey;
  549. ULONG cbToSearchKey = 0;
  550. ULONG ulPropArrayMax = 50;
  551. ULONG ulPropArrayCur = 0;
  552. FILETIME ft;
  553. wstring name, email;
  554. convert_context converter;
  555. /* We are almost there, we have the mail and the recipients. Now we should create the Message */
  556. hr = MAPIAllocateBuffer(sizeof(SPropValue) * ulPropArrayMax, &~lpPropArray);
  557. if (hr != hrSuccess)
  558. return hr;
  559. if (TryConvert(converter, (char*)lpecToUser->lpszFullName, name) != hrSuccess) {
  560. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to convert To name %s to widechar, using empty name in entryid", (char*)lpecToUser->lpszFullName);
  561. name.clear();
  562. }
  563. if (TryConvert(converter, (char*)lpecToUser->lpszMailAddress, email) != hrSuccess) {
  564. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to convert To email %s to widechar, using empty name in entryid", (char*)lpecToUser->lpszMailAddress);
  565. email.clear();
  566. }
  567. hr = ECCreateOneOff((LPTSTR)name.c_str(), (LPTSTR)L"SMTP", (LPTSTR)email.c_str(), MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO, &cbToEntryid, &~lpToEntryid);
  568. if (hr != hrSuccess) {
  569. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed creating one-off address: 0x%08X", hr);
  570. return hr;
  571. }
  572. hr = HrCreateEmailSearchKey("SMTP", (char*)lpecToUser->lpszMailAddress, &cbToSearchKey, &~lpToSearchKey);
  573. if (hr != hrSuccess) {
  574. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed creating email searchkey: 0x%08X", hr);
  575. return hr;
  576. }
  577. if (TryConvert(converter, (char*)lpecFromUser->lpszFullName, name) != hrSuccess) {
  578. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to convert From name %s to widechar, using empty name in entryid", (char*)lpecFromUser->lpszFullName);
  579. name.clear();
  580. }
  581. if (TryConvert(converter, (char*)lpecFromUser->lpszMailAddress, email) != hrSuccess) {
  582. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to convert From email %s to widechar, using empty name in entryid", (char*)lpecFromUser->lpszMailAddress);
  583. email.clear();
  584. }
  585. hr = ECCreateOneOff((LPTSTR)name.c_str(), (LPTSTR)L"SMTP", (LPTSTR)email.c_str(), MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO, &cbFromEntryid, &~lpFromEntryid);
  586. if(hr != hrSuccess) {
  587. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed creating one-off address: 0x%08X", hr);
  588. return hr;
  589. }
  590. hr = HrCreateEmailSearchKey("SMTP", (char*)lpecFromUser->lpszMailAddress, &cbFromSearchKey, &~lpFromSearchKey);
  591. if(hr != hrSuccess) {
  592. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed creating email searchkey: 0x%08X", hr);
  593. return hr;
  594. }
  595. /* Messageclass */
  596. lpPropArray[ulPropArrayCur].ulPropTag = PR_MESSAGE_CLASS_A;
  597. lpPropArray[ulPropArrayCur++].Value.lpszA = const_cast<char *>("IPM.Note.StorageQuotaWarning");
  598. /* Priority */
  599. lpPropArray[ulPropArrayCur].ulPropTag = PR_PRIORITY;
  600. lpPropArray[ulPropArrayCur++].Value.ul = PRIO_URGENT;
  601. /* Importance */
  602. lpPropArray[ulPropArrayCur].ulPropTag = PR_IMPORTANCE;
  603. lpPropArray[ulPropArrayCur++].Value.ul = IMPORTANCE_HIGH;
  604. /* Set message flags */
  605. lpPropArray[ulPropArrayCur].ulPropTag = PR_MESSAGE_FLAGS;
  606. lpPropArray[ulPropArrayCur++].Value.ul = MSGFLAG_UNMODIFIED;
  607. /* Subject */
  608. lpPropArray[ulPropArrayCur].ulPropTag = PR_SUBJECT_A;
  609. lpPropArray[ulPropArrayCur++].Value.lpszA = (LPSTR)strSubject.c_str();
  610. /* PR_CONVERSATION_TOPIC */
  611. lpPropArray[ulPropArrayCur].ulPropTag = PR_CONVERSATION_TOPIC_A;
  612. lpPropArray[ulPropArrayCur++].Value.lpszA = (LPSTR)strSubject.c_str();
  613. /* Body */
  614. lpPropArray[ulPropArrayCur].ulPropTag = PR_BODY_A;
  615. lpPropArray[ulPropArrayCur++].Value.lpszA = (LPSTR)strBody.c_str();
  616. /* RCVD_REPRESENTING_* properties */
  617. lpPropArray[ulPropArrayCur].ulPropTag = PR_RCVD_REPRESENTING_ADDRTYPE_A;
  618. lpPropArray[ulPropArrayCur++].Value.lpszA = const_cast<char *>("SMTP");
  619. lpPropArray[ulPropArrayCur].ulPropTag = PR_RCVD_REPRESENTING_EMAIL_ADDRESS_A;
  620. lpPropArray[ulPropArrayCur++].Value.lpszA = (lpecToUser->lpszMailAddress ? (LPSTR)lpecToUser->lpszMailAddress : (LPSTR)"");
  621. hr = MAPIAllocateMore(cbToEntryid, lpPropArray,
  622. (void**)&lpPropArray[ulPropArrayCur].Value.bin.lpb);
  623. if (hr != hrSuccess)
  624. return hr;
  625. lpPropArray[ulPropArrayCur].ulPropTag = PR_RCVD_REPRESENTING_ENTRYID;
  626. lpPropArray[ulPropArrayCur].Value.bin.cb = cbToEntryid;
  627. memcpy(lpPropArray[ulPropArrayCur++].Value.bin.lpb,
  628. lpToEntryid, cbToEntryid);
  629. lpPropArray[ulPropArrayCur].ulPropTag = PR_RCVD_REPRESENTING_NAME_A;
  630. lpPropArray[ulPropArrayCur++].Value.lpszA = (lpecToUser->lpszFullName ? (LPSTR)lpecToUser->lpszFullName : (LPSTR)"");
  631. hr = MAPIAllocateMore(cbToSearchKey, lpPropArray,
  632. (void**)&lpPropArray[ulPropArrayCur].Value.bin.lpb);
  633. if (hr != hrSuccess)
  634. return hr;
  635. lpPropArray[ulPropArrayCur].ulPropTag = PR_RCVD_REPRESENTING_SEARCH_KEY;
  636. lpPropArray[ulPropArrayCur].Value.bin.cb = cbToSearchKey;
  637. memcpy(lpPropArray[ulPropArrayCur++].Value.bin.lpb,
  638. lpToSearchKey, cbToSearchKey);
  639. /* RECEIVED_BY_* properties */
  640. lpPropArray[ulPropArrayCur].ulPropTag = PR_RECEIVED_BY_ADDRTYPE_A;
  641. lpPropArray[ulPropArrayCur++].Value.lpszA = const_cast<char *>("SMTP");
  642. lpPropArray[ulPropArrayCur].ulPropTag = PR_RECEIVED_BY_EMAIL_ADDRESS_A;
  643. lpPropArray[ulPropArrayCur++].Value.lpszA = (lpecToUser->lpszMailAddress ? (LPSTR)lpecToUser->lpszMailAddress : (LPSTR)"");
  644. hr = MAPIAllocateMore(cbToEntryid, lpPropArray,
  645. (void**)&lpPropArray[ulPropArrayCur].Value.bin.lpb);
  646. if (hr != hrSuccess)
  647. return hr;
  648. lpPropArray[ulPropArrayCur].ulPropTag = PR_RECEIVED_BY_ENTRYID;
  649. lpPropArray[ulPropArrayCur].Value.bin.cb = cbToEntryid;
  650. memcpy(lpPropArray[ulPropArrayCur++].Value.bin.lpb,
  651. lpToEntryid, cbToEntryid);
  652. lpPropArray[ulPropArrayCur].ulPropTag = PR_RECEIVED_BY_NAME_A;
  653. lpPropArray[ulPropArrayCur++].Value.lpszA = (lpecToUser->lpszFullName ? (LPSTR)lpecToUser->lpszFullName : (LPSTR)"");
  654. hr = MAPIAllocateMore(cbToSearchKey, lpPropArray,
  655. (void**)&lpPropArray[ulPropArrayCur].Value.bin.lpb);
  656. if (hr != hrSuccess)
  657. return hr;
  658. lpPropArray[ulPropArrayCur].ulPropTag = PR_RECEIVED_BY_SEARCH_KEY;
  659. lpPropArray[ulPropArrayCur].Value.bin.cb = cbToSearchKey;
  660. memcpy(lpPropArray[ulPropArrayCur++].Value.bin.lpb,
  661. lpToSearchKey, cbToSearchKey);
  662. /* System user, PR_SENDER* */
  663. lpPropArray[ulPropArrayCur].ulPropTag = PR_SENDER_ADDRTYPE_A;
  664. lpPropArray[ulPropArrayCur++].Value.lpszA = const_cast<char *>("SMTP");
  665. lpPropArray[ulPropArrayCur].ulPropTag = PR_SENDER_EMAIL_ADDRESS_A;
  666. lpPropArray[ulPropArrayCur++].Value.lpszA = (lpecFromUser->lpszMailAddress ? (LPSTR)lpecFromUser->lpszMailAddress : (LPSTR)"");
  667. hr = MAPIAllocateMore(cbFromEntryid, lpPropArray,
  668. (void**)&lpPropArray[ulPropArrayCur].Value.bin.lpb);
  669. if (hr != hrSuccess)
  670. return hr;
  671. lpPropArray[ulPropArrayCur].ulPropTag = PR_SENDER_ENTRYID;
  672. lpPropArray[ulPropArrayCur].Value.bin.cb = cbFromEntryid;
  673. memcpy(lpPropArray[ulPropArrayCur++].Value.bin.lpb,
  674. lpFromEntryid, cbFromEntryid);
  675. lpPropArray[ulPropArrayCur].ulPropTag = PR_SENDER_NAME_A;
  676. lpPropArray[ulPropArrayCur++].Value.lpszA = (lpecFromUser->lpszFullName ? (LPSTR)lpecFromUser->lpszFullName : (LPSTR)"kopano-system");
  677. hr = MAPIAllocateMore(cbFromSearchKey, lpPropArray,
  678. (void**)&lpPropArray[ulPropArrayCur].Value.bin.lpb);
  679. if (hr != hrSuccess)
  680. return hr;
  681. lpPropArray[ulPropArrayCur].ulPropTag = PR_SENDER_SEARCH_KEY;
  682. lpPropArray[ulPropArrayCur].Value.bin.cb = cbFromSearchKey;
  683. memcpy(lpPropArray[ulPropArrayCur++].Value.bin.lpb,
  684. lpFromSearchKey, cbFromSearchKey);
  685. /* System user, PR_SENT_REPRESENTING* */
  686. lpPropArray[ulPropArrayCur].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE_A;
  687. lpPropArray[ulPropArrayCur++].Value.lpszA = const_cast<char *>("SMTP");
  688. lpPropArray[ulPropArrayCur].ulPropTag = PR_SENT_REPRESENTING_EMAIL_ADDRESS_A;
  689. lpPropArray[ulPropArrayCur++].Value.lpszA = (lpecFromUser->lpszMailAddress ? (LPSTR)lpecFromUser->lpszMailAddress : (LPSTR)"");
  690. hr = MAPIAllocateMore(cbFromEntryid, lpPropArray,
  691. (void**)&lpPropArray[ulPropArrayCur].Value.bin.lpb);
  692. if (hr != hrSuccess)
  693. return hr;
  694. lpPropArray[ulPropArrayCur].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
  695. lpPropArray[ulPropArrayCur].Value.bin.cb = cbFromEntryid;
  696. memcpy(lpPropArray[ulPropArrayCur++].Value.bin.lpb,
  697. lpFromEntryid, cbFromEntryid);
  698. lpPropArray[ulPropArrayCur].ulPropTag = PR_SENT_REPRESENTING_NAME_A;
  699. lpPropArray[ulPropArrayCur++].Value.lpszA = (lpecFromUser->lpszFullName ? (LPSTR)lpecFromUser->lpszFullName : (LPSTR)"kopano-system");
  700. hr = MAPIAllocateMore(cbFromSearchKey, lpPropArray,
  701. (void**)&lpPropArray[ulPropArrayCur].Value.bin.lpb);
  702. if (hr != hrSuccess)
  703. return hr;
  704. lpPropArray[ulPropArrayCur].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
  705. lpPropArray[ulPropArrayCur].Value.bin.cb = cbFromSearchKey;
  706. memcpy(lpPropArray[ulPropArrayCur++].Value.bin.lpb,
  707. lpFromSearchKey, cbFromSearchKey);
  708. /* Get the time to add to the message as PR_CLIENT_SUBMIT_TIME */
  709. GetSystemTimeAsFileTime(&ft);
  710. /* Submit time */
  711. lpPropArray[ulPropArrayCur].ulPropTag = PR_CLIENT_SUBMIT_TIME;
  712. lpPropArray[ulPropArrayCur++].Value.ft = ft;
  713. /* Delivery time */
  714. lpPropArray[ulPropArrayCur].ulPropTag = PR_MESSAGE_DELIVERY_TIME;
  715. lpPropArray[ulPropArrayCur++].Value.ft = ft;
  716. assert(ulPropArrayCur <= ulPropArrayMax);
  717. *lppPropArray = lpPropArray.release();
  718. *lpcPropSize = ulPropArrayCur;
  719. return hrSuccess;
  720. }
  721. /**
  722. * Creates a recipient list to set in the IMessage as recipient table.
  723. *
  724. * @param[in] cToUsers Number of users in lpToUsers
  725. * @param[in] lpToUsers Structs of Kopano user information to write in the addresslist
  726. * @param[out] lppAddrList Addresslist for the recipient table
  727. * @retval MAPI_E_NOT_ENOUGH_MEMORY unable to allocate more memory
  728. */
  729. HRESULT ECQuotaMonitor::CreateRecipientList(ULONG cToUsers, ECUSER *lpToUsers,
  730. LPADRLIST *lppAddrList)
  731. {
  732. HRESULT hr = hrSuccess;
  733. adrlist_ptr lpAddrList;
  734. ULONG cbUserEntryid = 0;
  735. memory_ptr<ENTRYID> lpUserEntryid;
  736. ULONG cbUserSearchKey = 0;
  737. memory_ptr<unsigned char> lpUserSearchKey;
  738. hr = MAPIAllocateBuffer(CbNewADRLIST(cToUsers), &~lpAddrList);
  739. if(hr != hrSuccess)
  740. return hr;
  741. lpAddrList->cEntries = cToUsers;
  742. for (ULONG i = 0; i < cToUsers; ++i) {
  743. lpAddrList->aEntries[i].cValues = 7;
  744. hr = MAPIAllocateBuffer(sizeof(SPropValue) * lpAddrList->aEntries[i].cValues,
  745. (void**)&lpAddrList->aEntries[i].rgPropVals);
  746. if (hr != hrSuccess)
  747. return hr;
  748. hr = ECCreateOneOff((LPTSTR)lpToUsers[i].lpszFullName, (LPTSTR)"SMTP", (LPTSTR)lpToUsers[i].lpszMailAddress,
  749. MAPI_SEND_NO_RICH_INFO, &cbUserEntryid, &~lpUserEntryid);
  750. if (hr != hrSuccess) {
  751. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed creating one-off address: 0x%08X", hr);
  752. return hr;
  753. }
  754. hr = HrCreateEmailSearchKey("SMTP",
  755. reinterpret_cast<const char *>(lpToUsers[i].lpszMailAddress),
  756. &cbUserSearchKey, &~lpUserSearchKey);
  757. if (hr != hrSuccess) {
  758. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed creating email searchkey: 0x%08X", hr);
  759. return hr;
  760. }
  761. lpAddrList->aEntries[i].rgPropVals[0].ulPropTag = PR_ROWID;
  762. lpAddrList->aEntries[i].rgPropVals[0].Value.l = 0;
  763. lpAddrList->aEntries[i].rgPropVals[1].ulPropTag = PR_RECIPIENT_TYPE;
  764. lpAddrList->aEntries[i].rgPropVals[1].Value.l = ((i == 0) ? MAPI_TO : MAPI_CC);
  765. lpAddrList->aEntries[i].rgPropVals[2].ulPropTag = PR_DISPLAY_NAME_A;
  766. lpAddrList->aEntries[i].rgPropVals[2].Value.lpszA = (LPSTR)(lpToUsers[i].lpszFullName ? lpToUsers[i].lpszFullName : lpToUsers[i].lpszUsername);
  767. lpAddrList->aEntries[i].rgPropVals[3].ulPropTag = PR_EMAIL_ADDRESS_A;
  768. lpAddrList->aEntries[i].rgPropVals[3].Value.lpszA = (lpToUsers[i].lpszMailAddress ? (LPSTR)lpToUsers[i].lpszMailAddress : (LPSTR)"");
  769. lpAddrList->aEntries[i].rgPropVals[4].ulPropTag = PR_ADDRTYPE_A;
  770. lpAddrList->aEntries[i].rgPropVals[4].Value.lpszA = const_cast<char *>("SMTP");
  771. hr = MAPIAllocateMore(cbUserEntryid, lpAddrList->aEntries[i].rgPropVals,
  772. (void**)&lpAddrList->aEntries[i].rgPropVals[5].Value.bin.lpb);
  773. if (hr != hrSuccess)
  774. return hr;
  775. lpAddrList->aEntries[i].rgPropVals[5].ulPropTag = PR_ENTRYID;
  776. lpAddrList->aEntries[i].rgPropVals[5].Value.bin.cb = cbUserEntryid;
  777. memcpy(lpAddrList->aEntries[i].rgPropVals[5].Value.bin.lpb,
  778. lpUserEntryid, cbUserEntryid);
  779. hr = MAPIAllocateMore(cbUserSearchKey, lpAddrList->aEntries[i].rgPropVals,
  780. (void**)&lpAddrList->aEntries[i].rgPropVals[6].Value.bin.lpb);
  781. if (hr != hrSuccess)
  782. return hr;
  783. lpAddrList->aEntries[i].rgPropVals[6].ulPropTag = PR_SEARCH_KEY;
  784. lpAddrList->aEntries[i].rgPropVals[6].Value.bin.cb = cbUserSearchKey;
  785. memcpy(lpAddrList->aEntries[i].rgPropVals[6].Value.bin.lpb,
  786. lpUserSearchKey, cbUserSearchKey);
  787. }
  788. *lppAddrList = lpAddrList.release();
  789. return hrSuccess;
  790. }
  791. /**
  792. * Creates an email in the inbox of the given store with given properties and addresslist for recipients
  793. *
  794. * @param[in] lpMDB The store of a user to create the mail in the inbox folder
  795. * @param[in] cPropSize The number of properties in lpPropArray
  796. * @param[in] lpPropArray The properties to set in the quota mail
  797. * @param[in] lpAddrList The recipients to save in the recipients table
  798. * @return MAPI error code
  799. */
  800. HRESULT ECQuotaMonitor::SendQuotaWarningMail(IMsgStore* lpMDB, ULONG cPropSize, LPSPropValue lpPropArray, LPADRLIST lpAddrList)
  801. {
  802. HRESULT hr = hrSuccess;
  803. object_ptr<IMessage> lpMessage;
  804. ULONG cbEntryID = 0;
  805. memory_ptr<ENTRYID> lpEntryID;
  806. object_ptr<IMAPIFolder> lpInbox;
  807. ULONG ulObjType;
  808. /* Get the entry id of the inbox */
  809. hr = lpMDB->GetReceiveFolder((LPTSTR)"IPM", 0, &cbEntryID, &~lpEntryID, NULL);
  810. if (hr != hrSuccess) {
  811. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to resolve incoming folder, error code: 0x%08X", hr);
  812. return hr;
  813. }
  814. /* Open the inbox */
  815. hr = lpMDB->OpenEntry(cbEntryID, lpEntryID, &IID_IMAPIFolder, MAPI_MODIFY, &ulObjType, &~lpInbox);
  816. if (hr != hrSuccess || ulObjType != MAPI_FOLDER) {
  817. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to open inbox folder, error code: 0x%08X", hr);
  818. if(ulObjType != MAPI_FOLDER)
  819. return MAPI_E_NOT_FOUND;
  820. return hr;
  821. }
  822. /* Create a new message in the correct folder */
  823. hr = lpInbox->CreateMessage(nullptr, 0, &~lpMessage);
  824. if (hr != hrSuccess) {
  825. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to create new message, error code: %08X", hr);
  826. return hr;
  827. }
  828. hr = lpMessage->SetProps(cPropSize, lpPropArray, NULL);
  829. if(hr != hrSuccess) {
  830. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to set properties, error code: 0x%08X", hr);
  831. return hr;
  832. }
  833. hr = lpMessage->ModifyRecipients(MODRECIP_ADD, lpAddrList);
  834. if (hr != hrSuccess)
  835. return hr;
  836. /* Save Message */
  837. hr = lpMessage->SaveChanges(KEEP_OPEN_READWRITE);
  838. if (hr != hrSuccess)
  839. return hr;
  840. hr = HrNewMailNotification(lpMDB, lpMessage);
  841. if (hr != hrSuccess) {
  842. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to send 'New Mail' notification, error code: 0x%08X", hr);
  843. return hr;
  844. }
  845. return lpMDB->SaveChanges(KEEP_OPEN_READWRITE);
  846. }
  847. /**
  848. * Creates one quota mail
  849. *
  850. * @param[in] lpVars Template variables values
  851. * @param[in] lpMDB The store of the user in lpecToUser to create the mail in
  852. * @param[in] lpecToUser Kopano user information, will be placed in the To of the mail
  853. * @param[in] lpecFromUser Kopano user information, will be placed in the From of the mail
  854. * @param[in] lpAddrList Rows to set in the recipient table of the mail
  855. * @return MAPI error code
  856. */
  857. HRESULT ECQuotaMonitor::CreateQuotaWarningMail(TemplateVariables *lpVars,
  858. IMsgStore* lpMDB, ECUSER *lpecToUser, ECUSER *lpecFromUser,
  859. LPADRLIST lpAddrList)
  860. {
  861. HRESULT hr = hrSuccess;
  862. ULONG cPropSize = 0;
  863. memory_ptr<SPropValue> lpPropArray;
  864. string strSubject;
  865. string strBody;
  866. hr = CreateMailFromTemplate(lpVars, &strSubject, &strBody);
  867. if (hr != hrSuccess)
  868. return hr;
  869. hr = CreateMessageProperties(lpecToUser, lpecFromUser, strSubject, strBody, &cPropSize, &~lpPropArray);
  870. if (hr != hrSuccess)
  871. return hr;
  872. hr = SendQuotaWarningMail(lpMDB, cPropSize, lpPropArray, lpAddrList);
  873. if (hr != hrSuccess)
  874. return hr;
  875. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_NOTICE, "Mail delivered to user %s", (LPSTR)lpecToUser->lpszUsername);
  876. return hrSuccess;
  877. }
  878. /**
  879. * Opens the store of a company or user
  880. *
  881. * @param[in] szStoreName company or user name
  882. * @param[out] lppStore opened store
  883. *
  884. * @return MAPI Error code
  885. */
  886. HRESULT ECQuotaMonitor::OpenUserStore(LPTSTR szStoreName, objectclass_t objclass, LPMDB *lppStore)
  887. {
  888. HRESULT hr;
  889. ExchangeManageStorePtr ptrEMS;
  890. ULONG cbUserStoreEntryID = 0;
  891. EntryIdPtr ptrUserStoreEntryID;
  892. MsgStorePtr ptrStore;
  893. hr = m_lpMDBAdmin->QueryInterface(IID_IExchangeManageStore, &~ptrEMS);
  894. if (hr != hrSuccess)
  895. return hr;
  896. hr = ptrEMS->CreateStoreEntryID((LPTSTR)"", szStoreName,
  897. OPENSTORE_HOME_LOGON, &cbUserStoreEntryID, &~ptrUserStoreEntryID);
  898. if (hr != hrSuccess) {
  899. if (hr == MAPI_E_NOT_FOUND)
  900. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_INFO, "Store of %s \"%s\" not found", (objclass == CONTAINER_COMPANY) ? "company" : "user", reinterpret_cast<const char *>(szStoreName));
  901. else
  902. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get store entry id for \"%s\": %s (0x%08X)", reinterpret_cast<const char *>(szStoreName), GetMAPIErrorMessage(hr), hr);
  903. return hr;
  904. }
  905. hr = m_lpMAPIAdminSession->OpenMsgStore(0, cbUserStoreEntryID, ptrUserStoreEntryID, NULL, MDB_WRITE, lppStore);
  906. if (hr != hrSuccess) {
  907. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to open store for '%s', error code: 0x%08X", (LPSTR)szStoreName, hr);
  908. return hr;
  909. }
  910. return hrSuccess;
  911. }
  912. /**
  913. * Check the last mail time for the quota message.
  914. *
  915. * @param lpStore Store that is over quota
  916. *
  917. * @retval hrSuccess User should not receive quota message
  918. * @retval MAPI_E_TIMEOUT User should receive quota message
  919. * @return MAPI Error code
  920. */
  921. HRESULT ECQuotaMonitor::CheckQuotaInterval(LPMDB lpStore, LPMESSAGE *lppMessage, bool *lpbTimeout)
  922. {
  923. HRESULT hr;
  924. MessagePtr ptrMessage;
  925. SPropValuePtr ptrProp;
  926. const char *lpResendInterval = NULL;
  927. ULONG ulResendInterval = 0;
  928. FILETIME ft;
  929. FILETIME ftNextRun;
  930. hr = GetConfigMessage(lpStore, QUOTA_CONFIG_MSG, &~ptrMessage);
  931. if (hr != hrSuccess)
  932. return hr;
  933. hr = HrGetOneProp(ptrMessage, PR_EC_QUOTA_MAIL_TIME, &~ptrProp);
  934. if (hr == MAPI_E_NOT_FOUND) {
  935. *lppMessage = ptrMessage.release();
  936. *lpbTimeout = true;
  937. return hrSuccess;
  938. }
  939. if (hr != hrSuccess)
  940. return hr;
  941. /* Determine when the last warning mail was send, and if a new one should be send. */
  942. lpResendInterval = m_lpThreadMonitor->lpConfig->GetSetting("mailquota_resend_interval");
  943. ulResendInterval = (lpResendInterval && atoui(lpResendInterval) > 0) ? atoui(lpResendInterval) : 1;
  944. GetSystemTimeAsFileTime(&ft);
  945. UnixTimeToFileTime(FileTimeToUnixTime(ptrProp->Value.ft.dwHighDateTime, ptrProp->Value.ft.dwLowDateTime) +
  946. (ulResendInterval * 60 * 60 * 24) -(2 * 60), &ftNextRun);
  947. *lppMessage = ptrMessage.release();
  948. *lpbTimeout = (ft > ftNextRun);
  949. return hrSuccess;
  950. }
  951. /**
  952. * Writes the new timestamp in the store to now() when the last quota
  953. * mail was send to this store. The store must have been opened as
  954. * SYSTEM user, which can always write, eventhough the store is over
  955. * quota.
  956. *
  957. * @param[in] lpMDB Store to update the last quota send timestamp in
  958. * @return MAPI error code
  959. */
  960. HRESULT ECQuotaMonitor::UpdateQuotaTimestamp(LPMESSAGE lpMessage)
  961. {
  962. HRESULT hr;
  963. SPropValue sPropTime;
  964. FILETIME ft;
  965. GetSystemTimeAsFileTime(&ft);
  966. sPropTime.ulPropTag = PR_EC_QUOTA_MAIL_TIME;
  967. sPropTime.Value.ft = ft;
  968. hr = HrSetOneProp(lpMessage, &sPropTime);
  969. if (hr != hrSuccess)
  970. return hr;
  971. hr = lpMessage->SaveChanges(KEEP_OPEN_READWRITE);
  972. if (hr != hrSuccess) {
  973. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to save config message, error code: 0x%08X", hr);
  974. return hr;
  975. }
  976. return hrSuccess;
  977. }
  978. /**
  979. * Sends the quota mail to the user, and to any administrator listed
  980. * to receive quota information within the company space.
  981. *
  982. * @param[in] lpecUser The Kopano user who is over quota, NULL if the company is over quota
  983. * @param[in] lpecCompany The Kopano company of the lpecUser (default company if non-hosted), or the over quota company if lpecUser is NULL
  984. * @param[in] lpecQuotaStatus The quota status values of lpecUser or lpecCompany
  985. * @param[in] lpStore The store that is over quota
  986. * @return MAPI error code
  987. */
  988. HRESULT ECQuotaMonitor::Notify(ECUSER *lpecUser, ECCOMPANY *lpecCompany,
  989. ECQUOTASTATUS *lpecQuotaStatus, LPMDB lpStore)
  990. {
  991. HRESULT hr = hrSuccess;
  992. object_ptr<IECServiceAdmin> lpServiceAdmin;
  993. MsgStorePtr ptrRecipStore;
  994. MAPIFolderPtr ptrRoot;
  995. MessagePtr ptrQuotaTSMessage;
  996. bool bTimeout;
  997. memory_ptr<SPropValue> lpsObject;
  998. adrlist_ptr lpAddrList;
  999. memory_ptr<ECUSER> lpecFromUser, lpToUsers;
  1000. ULONG cToUsers = 0;
  1001. memory_ptr<ECQUOTA> lpecQuota;
  1002. ULONG cbUserId = 0;
  1003. LPENTRYID lpUserId = NULL;
  1004. struct TemplateVariables sVars;
  1005. // check if we need to send the actual email
  1006. hr = CheckQuotaInterval(lpStore, &~ptrQuotaTSMessage, &bTimeout);
  1007. if (hr != hrSuccess) {
  1008. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to query mail timeout value: 0x%08X", hr);
  1009. return hr;
  1010. }
  1011. if (!bTimeout) {
  1012. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_INFO, "Not sending message since the warning mail has already been sent in the past time interval");
  1013. return hr;
  1014. }
  1015. hr = HrGetOneProp(m_lpMDBAdmin, PR_EC_OBJECT, &~lpsObject);
  1016. if (hr != hrSuccess) {
  1017. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get internal object, error code: 0x%08X", hr);
  1018. return hr;
  1019. }
  1020. hr = reinterpret_cast<IECUnknown *>(lpsObject->Value.lpszA)->QueryInterface(IID_IECServiceAdmin, &~lpServiceAdmin);
  1021. if (hr != hrSuccess) {
  1022. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to get service admin, error code: 0x%08X", hr);
  1023. return hr;
  1024. }
  1025. hr = lpServiceAdmin->GetUser(lpecCompany->sAdministrator.cb, (LPENTRYID)lpecCompany->sAdministrator.lpb, 0, &~lpecFromUser);
  1026. if (hr != hrSuccess)
  1027. return hr;
  1028. if (lpecUser) {
  1029. cbUserId = lpecUser->sUserId.cb;
  1030. lpUserId = (LPENTRYID)lpecUser->sUserId.lpb;
  1031. sVars.ulClass = ACTIVE_USER;
  1032. sVars.strUserName = (LPSTR)lpecUser->lpszUsername;
  1033. sVars.strFullName = (LPSTR)lpecUser->lpszFullName;
  1034. sVars.strCompany = (LPSTR)lpecCompany->lpszCompanyname;
  1035. } else {
  1036. cbUserId = lpecCompany->sCompanyId.cb;
  1037. lpUserId = (LPENTRYID)lpecCompany->sCompanyId.lpb;
  1038. sVars.ulClass = CONTAINER_COMPANY;
  1039. sVars.strUserName = (LPSTR)lpecCompany->lpszCompanyname;
  1040. sVars.strFullName = (LPSTR)lpecCompany->lpszCompanyname;
  1041. sVars.strCompany = (LPSTR)lpecCompany->lpszCompanyname;
  1042. }
  1043. hr = lpServiceAdmin->GetQuota(cbUserId, lpUserId, false, &~lpecQuota);
  1044. if (hr != hrSuccess)
  1045. return hr;
  1046. sVars.ulStatus = lpecQuotaStatus->quotaStatus;
  1047. sVars.strStoreSize = str_storage(lpecQuotaStatus->llStoreSize);
  1048. sVars.strWarnSize = str_storage(lpecQuota->llWarnSize);
  1049. sVars.strSoftSize = str_storage(lpecQuota->llSoftSize);
  1050. sVars.strHardSize = str_storage(lpecQuota->llHardSize);
  1051. hr = lpServiceAdmin->GetQuotaRecipients(cbUserId, lpUserId, 0, &cToUsers, &~lpToUsers);
  1052. if (hr != hrSuccess)
  1053. return hr;
  1054. hr = CreateRecipientList(cToUsers, lpToUsers, &~lpAddrList);
  1055. if (hr != hrSuccess)
  1056. return hr;
  1057. /* Go through all stores to deliver the mail to all recipients.
  1058. *
  1059. * Note that we will parse the template for each recipient separately,
  1060. * this is done to support better language support later on where each user
  1061. * will get a notification mail in his prefered language.
  1062. */
  1063. for (ULONG i = 0; i < cToUsers; ++i) {
  1064. /* Company quota's shouldn't deliver to the first entry since that is the public store. */
  1065. if (i == 0 && sVars.ulClass == CONTAINER_COMPANY) {
  1066. if (cToUsers == 1)
  1067. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_ERROR, "No quota recipients for over quota company %s", (LPSTR)lpecCompany->lpszCompanyname);
  1068. continue;
  1069. }
  1070. if (OpenUserStore(lpToUsers[i].lpszUsername, sVars.ulClass, &~ptrRecipStore) != hrSuccess)
  1071. continue;
  1072. CreateQuotaWarningMail(&sVars, ptrRecipStore, &lpToUsers[i], lpecFromUser, lpAddrList);
  1073. }
  1074. if (UpdateQuotaTimestamp(ptrQuotaTSMessage) != hrSuccess)
  1075. m_lpThreadMonitor->lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to update last mail quota timestamp: 0x%08X", hr);
  1076. return hrSuccess;
  1077. }