rules.cpp 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217
  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 "rules.h"
  19. #include <mapi.h>
  20. #include <mapidefs.h>
  21. #include <mapicode.h>
  22. #include <mapiutil.h>
  23. #include <kopano/mapiext.h>
  24. #include <kopano/memory.hpp>
  25. #include <edkmdb.h>
  26. #include <edkguid.h>
  27. #include <kopano/ECGetText.h>
  28. #include <kopano/stringutil.h>
  29. #include <kopano/Util.h>
  30. #include <kopano/CommonUtil.h>
  31. #include <kopano/ECLogger.h>
  32. #include <kopano/MAPIErrors.h>
  33. #include <kopano/mapi_ptr.h>
  34. #include <kopano/mapiguidext.h>
  35. #include <kopano/charset/convert.h>
  36. #include <kopano/hl.hpp>
  37. #include "IECExchangeModifyTable.h"
  38. #include "PyMapiPlugin.h"
  39. #include "spmain.h"
  40. using namespace std;
  41. using namespace KCHL;
  42. static HRESULT GetRecipStrings(LPMESSAGE lpMessage, std::wstring &wstrTo,
  43. std::wstring &wstrCc, std::wstring &wstrBcc)
  44. {
  45. SRowSetPtr ptrRows;
  46. MAPITablePtr ptrRecips;
  47. static constexpr const SizedSPropTagArray(2, sptaDisplay) =
  48. {2, {PR_DISPLAY_NAME_W, PR_RECIPIENT_TYPE}};
  49. wstrTo.clear();
  50. wstrCc.clear();
  51. wstrBcc.clear();
  52. HRESULT hr = lpMessage->GetRecipientTable(MAPI_UNICODE, &~ptrRecips);
  53. if(hr != hrSuccess)
  54. return hr;
  55. hr = ptrRecips->SetColumns(sptaDisplay, TBL_BATCH);
  56. if(hr != hrSuccess)
  57. return hr;
  58. while(1) {
  59. hr = ptrRecips->QueryRows(1, 0, &ptrRows);
  60. if(hr != hrSuccess)
  61. return hr;
  62. if(ptrRows.size() == 0)
  63. break;
  64. if(ptrRows[0].lpProps[0].ulPropTag != PR_DISPLAY_NAME_W || ptrRows[0].lpProps[1].ulPropTag != PR_RECIPIENT_TYPE)
  65. continue;
  66. switch(ptrRows[0].lpProps[1].Value.ul) {
  67. case MAPI_TO:
  68. if (!wstrTo.empty()) wstrTo += L"; ";
  69. wstrTo += ptrRows[0].lpProps[0].Value.lpszW;
  70. break;
  71. case MAPI_CC:
  72. if (!wstrCc.empty()) wstrCc += L"; ";
  73. wstrCc += ptrRows[0].lpProps[0].Value.lpszW;
  74. break;
  75. case MAPI_BCC:
  76. if (!wstrBcc.empty()) wstrBcc += L"; ";
  77. wstrBcc += ptrRows[0].lpProps[0].Value.lpszW;
  78. break;
  79. }
  80. }
  81. return hrSuccess;
  82. }
  83. static HRESULT MungeForwardBody(LPMESSAGE lpMessage, LPMESSAGE lpOrigMessage)
  84. {
  85. SPropArrayPtr ptrBodies;
  86. static constexpr const SizedSPropTagArray(4, sBody) =
  87. {4, {PR_BODY_W, PR_HTML, PR_RTF_IN_SYNC, PR_INTERNET_CPID}};
  88. SPropArrayPtr ptrInfo;
  89. static constexpr const SizedSPropTagArray(4, sInfo) =
  90. {4, {PR_SENT_REPRESENTING_NAME_W,
  91. PR_SENT_REPRESENTING_EMAIL_ADDRESS_W, PR_MESSAGE_DELIVERY_TIME,
  92. PR_SUBJECT_W}};
  93. ULONG ulCharset;
  94. ULONG cValues;
  95. bool bPlain = false;
  96. SPropValue sNewBody;
  97. StreamPtr ptrStream;
  98. string strHTML;
  99. string strHTMLForwardText;
  100. wstring wstrBody;
  101. wstring strForwardText;
  102. wstring wstrTo, wstrCc, wstrBcc;
  103. HRESULT hr = lpOrigMessage->GetProps(sBody, 0, &cValues, &~ptrBodies);
  104. if (FAILED(hr))
  105. return hr;
  106. if (PROP_TYPE(ptrBodies[3].ulPropTag) != PT_ERROR)
  107. ulCharset = ptrBodies[3].Value.ul;
  108. else
  109. ulCharset = 20127; // US-ASCII
  110. if (PROP_TYPE(ptrBodies[0].ulPropTag) == PT_ERROR && PROP_TYPE(ptrBodies[1].ulPropTag) == PT_ERROR)
  111. // plain and html not found, check sync flag
  112. bPlain = (ptrBodies[2].Value.b == FALSE);
  113. else
  114. bPlain = PROP_TYPE(ptrBodies[1].ulPropTag) == PT_ERROR && ptrBodies[1].Value.err == MAPI_E_NOT_FOUND;
  115. sNewBody.ulPropTag = bPlain ? PR_BODY_W : PR_HTML;
  116. // From: <fullname>
  117. // Sent: <date>
  118. // To: <original To:>
  119. // Cc: <original Cc:>
  120. // Subject: <>
  121. // Auto forwarded by a rule
  122. hr = GetRecipStrings(lpOrigMessage, wstrTo, wstrCc, wstrBcc);
  123. if (FAILED(hr))
  124. return hr;
  125. hr = lpOrigMessage->GetProps(sInfo, 0, &cValues, &~ptrInfo);
  126. if (FAILED(hr))
  127. return hr;
  128. if (bPlain) {
  129. // Plain text body
  130. strForwardText = L"From: ";
  131. if (PROP_TYPE(ptrInfo[0].ulPropTag) != PT_ERROR)
  132. strForwardText += ptrInfo[0].Value.lpszW;
  133. else if (PROP_TYPE(ptrInfo[1].ulPropTag) != PT_ERROR)
  134. strForwardText += ptrInfo[1].Value.lpszW;
  135. if (PROP_TYPE(ptrInfo[1].ulPropTag) != PT_ERROR) {
  136. strForwardText += L" <";
  137. strForwardText += ptrInfo[1].Value.lpszW;
  138. strForwardText += L">";
  139. }
  140. strForwardText += L"\nSent: ";
  141. if (PROP_TYPE(ptrInfo[2].ulPropTag) != PT_ERROR) {
  142. WCHAR buffer[64];
  143. time_t t;
  144. struct tm date;
  145. FileTimeToUnixTime(ptrInfo[2].Value.ft, &t);
  146. localtime_r(&t, &date);
  147. wcsftime(buffer, ARRAY_SIZE(buffer), L"%c", &date);
  148. strForwardText += buffer;
  149. }
  150. strForwardText += L"\nTo: ";
  151. strForwardText += wstrTo;
  152. strForwardText += L"\nCc: ";
  153. strForwardText += wstrCc;
  154. strForwardText += L"\nSubject: ";
  155. if (PROP_TYPE(ptrInfo[3].ulPropTag) != PT_ERROR)
  156. strForwardText += ptrInfo[3].Value.lpszW;
  157. strForwardText += L"\nAuto forwarded by a rule\n\n";
  158. if (ptrBodies[0].ulPropTag == PT_ERROR) {
  159. hr = lpOrigMessage->OpenProperty(PR_BODY_W, &IID_IStream, 0, 0, &~ptrStream);
  160. if (hr == hrSuccess)
  161. hr = Util::HrStreamToString(ptrStream, wstrBody);
  162. // stream
  163. strForwardText.append(wstrBody);
  164. } else {
  165. strForwardText += ptrBodies[0].Value.lpszW;
  166. }
  167. sNewBody.Value.lpszW = (WCHAR*)strForwardText.c_str();
  168. }
  169. else {
  170. // HTML body (or rtf, but nuts to editing that!)
  171. string strFind("<body");
  172. const char* pos;
  173. hr = lpOrigMessage->OpenProperty(PR_HTML, &IID_IStream, 0, 0, &~ptrStream);
  174. if (hr == hrSuccess)
  175. hr = Util::HrStreamToString(ptrStream, strHTML);
  176. // icase <body> tag
  177. pos = str_ifind(strHTML.c_str(), strFind.c_str());
  178. pos = pos ? pos + strFind.length() : strHTML.c_str();
  179. // if body tag was not found, this will make it be placed after the first tag, probably <html>
  180. if ((pos == strHTML.c_str() && *pos == '<') || pos != strHTML.c_str()) {
  181. // not all html bodies start actually using tags, so only seek if we find a <, or if we found a body tag starting point.
  182. while (*pos && *pos != '>')
  183. ++pos;
  184. if (*pos == '>')
  185. ++pos;
  186. }
  187. strHTMLForwardText = "<b>From:</b> ";
  188. if (PROP_TYPE(ptrInfo[0].ulPropTag) != PT_ERROR)
  189. Util::HrTextToHtml(ptrInfo[0].Value.lpszW, strHTMLForwardText, ulCharset);
  190. else if (PROP_TYPE(ptrInfo[1].ulPropTag) != PT_ERROR)
  191. Util::HrTextToHtml(ptrInfo[1].Value.lpszW, strHTMLForwardText, ulCharset);
  192. if (PROP_TYPE(ptrInfo[1].ulPropTag) != PT_ERROR) {
  193. strHTMLForwardText += " &lt;<a href=\"mailto:";
  194. Util::HrTextToHtml(ptrInfo[1].Value.lpszW, strHTMLForwardText, ulCharset);
  195. strHTMLForwardText += "\">";
  196. Util::HrTextToHtml(ptrInfo[1].Value.lpszW, strHTMLForwardText, ulCharset);
  197. strHTMLForwardText += "</a>&gt;";
  198. }
  199. strHTMLForwardText += "<br><b>Sent:</b> ";
  200. if (PROP_TYPE(ptrInfo[2].ulPropTag) != PT_ERROR) {
  201. char buffer[32];
  202. time_t t;
  203. struct tm date;
  204. FileTimeToUnixTime(ptrInfo[2].Value.ft, &t);
  205. localtime_r(&t, &date);
  206. strftime(buffer, 32, "%c", &date);
  207. strHTMLForwardText += buffer;
  208. }
  209. strHTMLForwardText += "<br><b>To:</b> ";
  210. Util::HrTextToHtml(wstrTo.c_str(), strHTMLForwardText, ulCharset);
  211. strHTMLForwardText += "<br><b>Cc:</b> ";
  212. Util::HrTextToHtml(wstrCc.c_str(), strHTMLForwardText, ulCharset);
  213. strHTMLForwardText += "<br><b>Subject:</b> ";
  214. if (PROP_TYPE(ptrInfo[3].ulPropTag) != PT_ERROR)
  215. Util::HrTextToHtml(ptrInfo[3].Value.lpszW, strHTMLForwardText, ulCharset);
  216. strHTMLForwardText += "<br><b>Auto forwarded by a rule</b><br><hr><br>";
  217. strHTML.insert((pos - strHTML.c_str()), strHTMLForwardText);
  218. sNewBody.Value.bin.cb = strHTML.size();
  219. sNewBody.Value.bin.lpb = (BYTE*)strHTML.c_str();
  220. }
  221. // set new body with forward markers
  222. return lpMessage->SetProps(1, &sNewBody, NULL);
  223. }
  224. static HRESULT CreateOutboxMessage(LPMDB lpOrigStore, LPMESSAGE *lppMessage)
  225. {
  226. HRESULT hr = hrSuccess;
  227. object_ptr<IMAPIFolder> lpOutbox;
  228. memory_ptr<SPropValue> lpOutboxEntryID;
  229. ULONG ulObjType = 0;
  230. hr = HrGetOneProp(lpOrigStore, PR_IPM_OUTBOX_ENTRYID, &~lpOutboxEntryID);
  231. if (hr != hrSuccess)
  232. return hr;
  233. hr = lpOrigStore->OpenEntry(lpOutboxEntryID->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpOutboxEntryID->Value.bin.lpb), nullptr, MAPI_MODIFY, &ulObjType, &~lpOutbox);
  234. if (hr != hrSuccess)
  235. return hr;
  236. return lpOutbox->CreateMessage(nullptr, 0, lppMessage);
  237. }
  238. static HRESULT CreateReplyCopy(LPMAPISESSION lpSession, LPMDB lpOrigStore,
  239. IMAPIProp *lpOrigMessage, LPMESSAGE lpTemplate, LPMESSAGE *lppMessage)
  240. {
  241. HRESULT hr = hrSuccess;
  242. object_ptr<IMessage> lpReplyMessage;
  243. memory_ptr<SPropValue> lpProp, lpFrom, lpReplyRecipient;
  244. memory_ptr<SPropValue> lpSentMailEntryID;
  245. std::wstring strwSubject;
  246. ULONG cValues = 0;
  247. SizedADRLIST(1, sRecip) = {0, {}};
  248. ULONG ulCmp = 0;
  249. static constexpr const SizedSPropTagArray(5, sFrom) =
  250. {5, {PR_RECEIVED_BY_ENTRYID, PR_RECEIVED_BY_NAME,
  251. PR_RECEIVED_BY_ADDRTYPE, PR_RECEIVED_BY_EMAIL_ADDRESS,
  252. PR_RECEIVED_BY_SEARCH_KEY}};
  253. static constexpr const SizedSPropTagArray(6, sReplyRecipient) =
  254. {6, {PR_SENDER_ENTRYID, PR_SENDER_NAME, PR_SENDER_ADDRTYPE,
  255. PR_SENDER_EMAIL_ADDRESS, PR_SENDER_SEARCH_KEY, PR_NULL}};
  256. hr = CreateOutboxMessage(lpOrigStore, &~lpReplyMessage);
  257. if (hr != hrSuccess)
  258. goto exitpm;
  259. hr = lpTemplate->CopyTo(0, NULL, NULL, 0, NULL, &IID_IMessage, lpReplyMessage, 0, NULL);
  260. if (hr != hrSuccess)
  261. goto exitpm;
  262. // set "sent mail" folder entryid for spooler
  263. hr = HrGetOneProp(lpOrigStore, PR_IPM_SENTMAIL_ENTRYID, &~lpSentMailEntryID);
  264. if (hr != hrSuccess)
  265. goto exitpm;
  266. lpSentMailEntryID->ulPropTag = PR_SENTMAIL_ENTRYID;
  267. hr = HrSetOneProp(lpReplyMessage, lpSentMailEntryID);
  268. if (hr != hrSuccess)
  269. goto exitpm;
  270. // set a sensible subject
  271. hr = HrGetOneProp(lpReplyMessage, PR_SUBJECT_W, &~lpProp);
  272. if (hr == hrSuccess && lpProp->Value.lpszW[0] == L'\0') {
  273. // Exchange: uses "BT: orig subject" if empty, or only subject from template.
  274. hr = HrGetOneProp(lpOrigMessage, PR_SUBJECT_W, &~lpProp);
  275. if (hr == hrSuccess) {
  276. strwSubject = wstring(L"BT: ") + lpProp->Value.lpszW;
  277. lpProp->Value.lpszW = (WCHAR*)strwSubject.c_str();
  278. hr = HrSetOneProp(lpReplyMessage, lpProp);
  279. if (hr != hrSuccess)
  280. goto exitpm;
  281. }
  282. }
  283. hr = HrGetOneProp(lpOrigMessage, PR_INTERNET_MESSAGE_ID, &~lpProp);
  284. if (hr == hrSuccess) {
  285. lpProp->ulPropTag = PR_IN_REPLY_TO_ID;
  286. hr = HrSetOneProp(lpReplyMessage, lpProp);
  287. if (hr != hrSuccess)
  288. goto exitpm;
  289. }
  290. // set From to self
  291. hr = lpOrigMessage->GetProps(sFrom, 0, &cValues, &~lpFrom);
  292. if (FAILED(hr))
  293. goto exitpm;
  294. lpFrom[0].ulPropTag = CHANGE_PROP_TYPE(PR_SENT_REPRESENTING_ENTRYID, PROP_TYPE(lpFrom[0].ulPropTag));
  295. lpFrom[1].ulPropTag = CHANGE_PROP_TYPE(PR_SENT_REPRESENTING_NAME, PROP_TYPE(lpFrom[1].ulPropTag));
  296. lpFrom[2].ulPropTag = CHANGE_PROP_TYPE(PR_SENT_REPRESENTING_ADDRTYPE, PROP_TYPE(lpFrom[2].ulPropTag));
  297. lpFrom[3].ulPropTag = CHANGE_PROP_TYPE(PR_SENT_REPRESENTING_EMAIL_ADDRESS, PROP_TYPE(lpFrom[3].ulPropTag));
  298. lpFrom[4].ulPropTag = CHANGE_PROP_TYPE(PR_SENT_REPRESENTING_SEARCH_KEY, PROP_TYPE(lpFrom[4].ulPropTag));
  299. hr = lpReplyMessage->SetProps(5, lpFrom, NULL);
  300. if (FAILED(hr))
  301. goto exitpm;
  302. if (parseBool(g_lpConfig->GetSetting("set_rule_headers", NULL, "yes"))) {
  303. SPropValue sPropVal;
  304. PROPMAP_START(1)
  305. PROPMAP_NAMED_ID(KopanoRuleAction, PT_UNICODE, PS_INTERNET_HEADERS, "x-kopano-rule-action")
  306. PROPMAP_INIT(lpReplyMessage);
  307. sPropVal.ulPropTag = PROP_KopanoRuleAction;
  308. sPropVal.Value.lpszW = const_cast<wchar_t *>(L"reply");
  309. hr = HrSetOneProp(lpReplyMessage, &sPropVal);
  310. if (hr != hrSuccess)
  311. goto exitpm;
  312. }
  313. // append To with original sender
  314. // @todo get Reply-To ?
  315. hr = lpOrigMessage->GetProps(sReplyRecipient, 0, &cValues, &~lpReplyRecipient);
  316. if (FAILED(hr))
  317. goto exitpm;
  318. // obvious loop is being obvious
  319. if (PROP_TYPE(lpReplyRecipient[0].ulPropTag) != PT_ERROR && PROP_TYPE(lpFrom[0].ulPropTag ) != PT_ERROR) {
  320. hr = lpSession->CompareEntryIDs(lpReplyRecipient[0].Value.bin.cb, (LPENTRYID)lpReplyRecipient[0].Value.bin.lpb,
  321. lpFrom[0].Value.bin.cb, (LPENTRYID)lpFrom[0].Value.bin.lpb, 0, &ulCmp);
  322. if (hr == hrSuccess && ulCmp == TRUE) {
  323. hr = MAPI_E_UNABLE_TO_COMPLETE;
  324. goto exitpm;
  325. }
  326. }
  327. lpReplyRecipient[0].ulPropTag = CHANGE_PROP_TYPE(PR_ENTRYID, PROP_TYPE(lpReplyRecipient[0].ulPropTag));
  328. lpReplyRecipient[1].ulPropTag = CHANGE_PROP_TYPE(PR_DISPLAY_NAME, PROP_TYPE(lpReplyRecipient[1].ulPropTag));
  329. lpReplyRecipient[2].ulPropTag = CHANGE_PROP_TYPE(PR_ADDRTYPE, PROP_TYPE(lpReplyRecipient[2].ulPropTag));
  330. lpReplyRecipient[3].ulPropTag = CHANGE_PROP_TYPE(PR_EMAIL_ADDRESS, PROP_TYPE(lpReplyRecipient[3].ulPropTag));
  331. lpReplyRecipient[4].ulPropTag = CHANGE_PROP_TYPE(PR_SEARCH_KEY, PROP_TYPE(lpReplyRecipient[4].ulPropTag));
  332. lpReplyRecipient[5].ulPropTag = PR_RECIPIENT_TYPE;
  333. lpReplyRecipient[5].Value.ul = MAPI_TO;
  334. sRecip.cEntries = 1;
  335. sRecip.aEntries[0].cValues = cValues;
  336. sRecip.aEntries[0].rgPropVals = lpReplyRecipient;
  337. hr = lpReplyMessage->ModifyRecipients(MODRECIP_ADD, sRecip);
  338. if (FAILED(hr))
  339. goto exitpm;
  340. // return message
  341. hr = lpReplyMessage->QueryInterface(IID_IMessage, (void**)lppMessage);
  342. exitpm:
  343. return hr;
  344. }
  345. /**
  346. * @pat: pattern
  347. * @subj: subject string (domain part of an e-mail address)
  348. *
  349. * This _is_ different from kc_wildcard_cmp in that cmp2 allows
  350. * the asterisk to match dots.
  351. */
  352. static bool kc_wildcard_cmp2(const char *pat, const char *subj)
  353. {
  354. while (*pat != '\0' && *subj != '\0') {
  355. if (*pat == '*') {
  356. ++pat;
  357. for (; *subj != '\0'; ++subj)
  358. if (kc_wildcard_cmp2(pat, subj))
  359. return true;
  360. continue;
  361. }
  362. if (tolower(*pat) != tolower(*subj))
  363. return false;
  364. ++pat;
  365. ++subj;
  366. }
  367. return *pat == '\0' && *subj == '\0';
  368. }
  369. static bool proc_fwd_allowed(const std::vector<std::string> &wdomlist,
  370. const char *addr)
  371. {
  372. const char *p = strchr(addr, '@');
  373. if (p == nullptr) {
  374. ec_log_err("K-1900: Address \"%s\" had no '@', aborting forward because we could not possibly match it to a domain whitelist.", addr);
  375. return false;
  376. }
  377. addr = p + 1;
  378. for (const auto &wdomstr : wdomlist) {
  379. const char *wdom = wdomstr.c_str();
  380. if (strcmp(wdom, "*") == 0 /* fastpath */ ||
  381. kc_wildcard_cmp2(wdom, addr)) {
  382. ec_log_info("K-1901: proc_fwd_allowed: \"%s\" whitelist match on \"%s\"", addr, wdom);
  383. return true;
  384. }
  385. }
  386. ec_log_info("K-1902: proc_fwd_allowed: \"%s\" matched nothing on the whitelist", addr);
  387. return false;
  388. }
  389. /**
  390. * @inbox: folder to dump warning mail into
  391. * @addr: target address which delivery was rejected to
  392. *
  393. * Drop an additional message into @inbox informing about the
  394. * unwillingness to process a forwarding rule.
  395. */
  396. static HRESULT kc_send_fwdabort_notice(KCHL::KStore &&store, const char *addr)
  397. {
  398. using namespace KCHL;
  399. KMessage msg;
  400. try {
  401. KFolder inbox = store.open_entry(store.get_receive_folder(), nullptr, MAPI_MODIFY);
  402. msg = inbox.create_message(nullptr, 0);
  403. } catch (KCHL::KMAPIError &err) {
  404. ec_log_warn("K-2382: create_message: 0x%08x %s", err.code(), err.what());
  405. return err.code();
  406. }
  407. SPropValue prop[7];
  408. size_t nprop = 0;
  409. prop[nprop].ulPropTag = PR_SENDER_NAME_W;
  410. prop[nprop++].Value.lpszW = const_cast<wchar_t *>(L"Mail Delivery System");
  411. prop[nprop].ulPropTag = PR_SUBJECT_W;
  412. prop[nprop++].Value.lpszW = const_cast<wchar_t *>(L"Forwarding rule has been abrogated");
  413. prop[nprop].ulPropTag = PR_MESSAGE_FLAGS;
  414. prop[nprop++].Value.ul = 0;
  415. prop[nprop].ulPropTag = PR_MESSAGE_CLASS_W;
  416. prop[nprop++].Value.lpszW = const_cast<wchar_t *>(L"REPORT.IPM.Note.NDR");
  417. FILETIME ft;
  418. GetSystemTimeAsFileTime(&ft);
  419. prop[nprop].ulPropTag = PR_CLIENT_SUBMIT_TIME;
  420. prop[nprop++].Value.ft = ft;
  421. prop[nprop].ulPropTag = PR_MESSAGE_DELIVERY_TIME;
  422. prop[nprop++].Value.ft = ft;
  423. std::wstring newbody = convert_to<std::wstring>(format(_A(
  424. "The Kopano mail system is rejecting your request "
  425. "to forward e-mails (via mail filters) to <%s>: "
  426. "the operation is not permitted.\n\n"), addr) +
  427. _A("Remove the rule or contact your administrator about the "
  428. "\"forward_whitelist_domains\" setting.\n"));
  429. prop[nprop].ulPropTag = PR_BODY_W;
  430. prop[nprop++].Value.lpszW = const_cast<wchar_t *>(newbody.c_str());
  431. auto hr = msg->SetProps(nprop, prop, nullptr);
  432. if (hr != hrSuccess) {
  433. ec_log_err("K-3283: SetProps: 0x%08x %s", hr, GetMAPIErrorMessage(hr));
  434. return hr;
  435. }
  436. hr = msg->SaveChanges(KEEP_OPEN_READONLY);
  437. if (hr != hrSuccess) {
  438. ec_log_err("K-3284: commit: 0x%08x %s", hr, GetMAPIErrorMessage(hr));
  439. return hr;
  440. }
  441. hr = HrNewMailNotification(store, msg);
  442. if (hr != hrSuccess)
  443. ec_log_warn("K-3285: NewMailNotification: 0x%08x %s", hr, GetMAPIErrorMessage(hr));
  444. return hr;
  445. }
  446. /**
  447. * Checks the rule recipient list for a possible loop, and filters
  448. * that recipient. Returns an error when no recipients are left after
  449. * the filter.
  450. *
  451. * @param[in] lpMessage The original delivered message performing the rule action
  452. * @param[in] lpRuleRecipients The recipient list from the rule
  453. * @param[in] bOpDelegate If the action a delegate or forward action
  454. * @param[out] lppNewRecipients The actual recipient list to perform the action on
  455. *
  456. * @return MAPI error code
  457. */
  458. static HRESULT CheckRecipients(IAddrBook *lpAdrBook, IMsgStore *orig_store,
  459. IMAPIProp *lpMessage, const ADRLIST *lpRuleRecipients, bool bOpDelegate,
  460. bool bIncludeAsP1, ADRLIST **lppNewRecipients)
  461. {
  462. HRESULT hr = hrSuccess;
  463. adrlist_ptr lpRecipients;
  464. memory_ptr<SPropValue> lpMsgClass;
  465. std::wstring strFromName, strFromType, strFromAddress;
  466. std::wstring strRuleName, strRuleType, strRuleAddress;
  467. hr = HrGetAddress(lpAdrBook, (IMessage*)lpMessage,
  468. PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME_W,
  469. PR_SENT_REPRESENTING_ADDRTYPE_W, PR_SENT_REPRESENTING_EMAIL_ADDRESS_W,
  470. strFromName, strFromType, strFromAddress);
  471. if (hr != hrSuccess) {
  472. ec_log_err("Unable to get from address 0x%08X", hr);
  473. return hr;
  474. }
  475. hr = MAPIAllocateBuffer(CbNewADRLIST(lpRuleRecipients->cEntries), &~lpRecipients);
  476. if (hr != hrSuccess) {
  477. ec_log_err("CheckRecipients(): MAPIAllocateBuffer failed %x", hr);
  478. return hr;
  479. }
  480. std::vector<std::string> fwd_whitelist =
  481. tokenize(g_lpConfig->GetSetting("forward_whitelist_domains"), " ");
  482. HrGetOneProp(lpMessage, PR_MESSAGE_CLASS_A, &~lpMsgClass); //ignore errors
  483. lpRecipients->cEntries = 0;
  484. for (ULONG i = 0; i < lpRuleRecipients->cEntries; ++i) {
  485. hr = HrGetAddress(lpAdrBook, lpRuleRecipients->aEntries[i].rgPropVals, lpRuleRecipients->aEntries[i].cValues, PR_ENTRYID,
  486. CHANGE_PROP_TYPE(PR_DISPLAY_NAME, PT_UNSPECIFIED), CHANGE_PROP_TYPE(PR_ADDRTYPE, PT_UNSPECIFIED), CHANGE_PROP_TYPE(PR_SMTP_ADDRESS, PT_UNSPECIFIED),
  487. strRuleName, strRuleType, strRuleAddress);
  488. if (hr != hrSuccess) {
  489. ec_log_err("Unable to get rule address 0x%08X", hr);
  490. return hr;
  491. }
  492. auto rule_addr_std = convert_to<std::string>(strRuleAddress);
  493. if (!proc_fwd_allowed(fwd_whitelist, rule_addr_std.c_str())) {
  494. kc_send_fwdabort_notice(orig_store, rule_addr_std.c_str());
  495. return MAPI_E_NO_ACCESS;
  496. }
  497. if (strFromAddress == strRuleAddress &&
  498. // Hack for Meeting requests
  499. (!bOpDelegate || lpMsgClass == nullptr ||
  500. strstr(lpMsgClass->Value.lpszA, "IPM.Schedule.Meeting.") == nullptr)) {
  501. ec_log_info("Same user found in From and rule, blocking for loop protection");
  502. continue;
  503. }
  504. // copy recipient
  505. hr = Util::HrCopyPropertyArray(lpRuleRecipients->aEntries[i].rgPropVals, lpRuleRecipients->aEntries[i].cValues, &lpRecipients->aEntries[lpRecipients->cEntries].rgPropVals, &lpRecipients->aEntries[lpRecipients->cEntries].cValues, true);
  506. if (hr != hrSuccess) {
  507. ec_log_err("CheckRecipients(): Util::HrCopyPropertyArray failed %x", hr);
  508. return hr;
  509. }
  510. if(bIncludeAsP1) {
  511. auto lpRecipType = PpropFindProp(lpRecipients->aEntries[lpRecipients->cEntries].rgPropVals, lpRecipients->aEntries[lpRecipients->cEntries].cValues, PR_RECIPIENT_TYPE);
  512. if(!lpRecipType) {
  513. ec_log_crit("Attempt to add recipient with no PR_RECIPIENT_TYPE");
  514. return MAPI_E_INVALID_PARAMETER;
  515. }
  516. lpRecipType->Value.ul = MAPI_P1;
  517. }
  518. ++lpRecipients->cEntries;
  519. }
  520. if (lpRecipients->cEntries == 0) {
  521. ec_log_warn("Loop protection blocked all recipients, skipping rule");
  522. return MAPI_E_UNABLE_TO_COMPLETE;
  523. }
  524. if (lpRecipients->cEntries != lpRuleRecipients->cEntries)
  525. ec_log_info("Loop protection blocked some recipients");
  526. *lppNewRecipients = lpRecipients.release();
  527. lpRecipients = NULL;
  528. return hrSuccess;
  529. }
  530. static HRESULT CreateForwardCopy(IAddrBook *lpAdrBook, IMsgStore *lpOrigStore,
  531. IMessage *lpOrigMessage, const ADRLIST *lpRecipients,
  532. bool bOpDelegate, bool bDoPreserveSender, bool bDoNotMunge,
  533. bool bForwardAsAttachment, IMessage **lppMessage)
  534. {
  535. HRESULT hr = hrSuccess;
  536. LPMESSAGE lpFwdMsg = NULL;
  537. memory_ptr<SPropValue> lpSentMailEntryID, lpOrigSubject;
  538. memory_ptr<SPropTagArray> lpExclude;
  539. adrlist_ptr filtered_recips;
  540. ULONG ulANr = 0;
  541. static constexpr const SizedSPropTagArray(10, sExcludeFromCopyForward) = {10, {
  542. PR_TRANSPORT_MESSAGE_HEADERS,
  543. PR_SENT_REPRESENTING_ENTRYID,
  544. PR_SENT_REPRESENTING_NAME,
  545. PR_SENT_REPRESENTING_ADDRTYPE,
  546. PR_SENT_REPRESENTING_EMAIL_ADDRESS,
  547. PR_SENT_REPRESENTING_SEARCH_KEY,
  548. PR_READ_RECEIPT_REQUESTED,
  549. PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED,
  550. PR_MESSAGE_FLAGS,
  551. PR_MESSAGE_RECIPIENTS, // This must be the last entry, see bDoNotMunge
  552. } };
  553. static constexpr const SizedSPropTagArray (3, sExcludeFromCopyRedirect) = {3, {
  554. PR_TRANSPORT_MESSAGE_HEADERS,
  555. PR_MESSAGE_FLAGS,
  556. PR_MESSAGE_RECIPIENTS, // This must be the last entry, see bDoNotMunge
  557. } };
  558. static constexpr const SizedSPropTagArray(1, sExcludeFromAttachedForward) =
  559. {1, {PR_TRANSPORT_MESSAGE_HEADERS}};
  560. SPropValue sForwardProps[5];
  561. ULONG cfp = 0;
  562. wstring strSubject;
  563. if (lpRecipients == NULL || lpRecipients->cEntries == 0) {
  564. ec_log_crit("No rule recipient");
  565. hr = MAPI_E_INVALID_PARAMETER;
  566. goto exitpm;
  567. }
  568. hr = CheckRecipients(lpAdrBook, lpOrigStore, lpOrigMessage, lpRecipients,
  569. bOpDelegate, bDoNotMunge, &~filtered_recips);
  570. if (hr == MAPI_E_NO_ACCESS) {
  571. ec_log_info("K-1904: Forwarding not permitted. Ending rule processing.");
  572. goto exitpm;
  573. }
  574. if (hr == MAPI_E_UNABLE_TO_COMPLETE)
  575. goto exitpm;
  576. if (hr == hrSuccess)
  577. lpRecipients = filtered_recips.get();
  578. hr = HrGetOneProp(lpOrigStore, PR_IPM_SENTMAIL_ENTRYID, &~lpSentMailEntryID);
  579. if (hr != hrSuccess)
  580. goto exitpm;
  581. hr = CreateOutboxMessage(lpOrigStore, &lpFwdMsg);
  582. if (hr != hrSuccess)
  583. goto exitpm;
  584. // If we're doing a redirect, copy over the original PR_SENT_REPRESENTING_*, otherwise don't
  585. hr = Util::HrCopyPropTagArray(bDoPreserveSender ? sExcludeFromCopyRedirect : sExcludeFromCopyForward, &~lpExclude);
  586. if (hr != hrSuccess)
  587. return hr;
  588. if(bDoNotMunge) {
  589. // The idea here is to enable 'resend' mode and to include the original recipient list. What will
  590. // happen is that the original recipient list will be used to generate the headers of the message, but
  591. // only the MAPI_P1 recipients will be used to send the message to. This is exactly what we want. So
  592. // with bDoNotMunge, we copy the original recipient from the original message, and set MSGFLAG_RESEND.
  593. // Later on, we set the actual recipient to MAPI_P1
  594. SPropValue sPropResend;
  595. sPropResend.ulPropTag = PR_MESSAGE_FLAGS;
  596. sPropResend.Value.ul = MSGFLAG_UNSENT | MSGFLAG_RESEND | MSGFLAG_READ;
  597. --lpExclude->cValues; // strip PR_MESSAGE_RECIPIENTS, since original recipients should be used
  598. hr = HrSetOneProp(lpFwdMsg, &sPropResend);
  599. if(hr != hrSuccess)
  600. goto exitpm;
  601. }
  602. if (bForwardAsAttachment) {
  603. object_ptr<IAttach> lpAttach;
  604. object_ptr<IMessage> lpAttachMsg;
  605. hr = lpFwdMsg->CreateAttach(nullptr, 0, &ulANr, &~lpAttach);
  606. if (hr != hrSuccess)
  607. goto exitpm;
  608. SPropValue sAttachMethod;
  609. sAttachMethod.ulPropTag = PR_ATTACH_METHOD;
  610. sAttachMethod.Value.ul = ATTACH_EMBEDDED_MSG;
  611. hr = lpAttach->SetProps(1, &sAttachMethod, NULL);
  612. if (hr != hrSuccess)
  613. goto exitpm;
  614. hr = lpAttach->OpenProperty(PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, MAPI_CREATE | MAPI_MODIFY, &~lpAttachMsg);
  615. if (hr != hrSuccess)
  616. goto exitpm;
  617. hr = lpOrigMessage->CopyTo(0, NULL, sExcludeFromAttachedForward,
  618. 0, NULL, &IID_IMessage, lpAttachMsg, 0, NULL);
  619. if (hr != hrSuccess)
  620. goto exitpm;
  621. hr = lpAttachMsg->SaveChanges(0);
  622. if (hr != hrSuccess)
  623. goto exitpm;
  624. hr = lpAttach->SaveChanges(0);
  625. if (hr != hrSuccess)
  626. goto exitpm;
  627. }
  628. else {
  629. hr = lpOrigMessage->CopyTo(0, NULL, lpExclude, 0, NULL, &IID_IMessage, lpFwdMsg, 0, NULL);
  630. if (hr != hrSuccess)
  631. goto exitpm;
  632. }
  633. hr = lpFwdMsg->ModifyRecipients(MODRECIP_ADD, lpRecipients);
  634. if (hr != hrSuccess)
  635. goto exitpm;
  636. // set from email ??
  637. hr = HrGetOneProp(lpOrigMessage, PR_SUBJECT, &~lpOrigSubject);
  638. if (hr == hrSuccess)
  639. strSubject = lpOrigSubject->Value.lpszW;
  640. if(!bDoNotMunge || bForwardAsAttachment)
  641. strSubject.insert(0, L"FW: ");
  642. cfp = 0;
  643. sForwardProps[cfp].ulPropTag = PR_AUTO_FORWARDED;
  644. sForwardProps[cfp++].Value.b = TRUE;
  645. sForwardProps[cfp].ulPropTag = PR_SUBJECT;
  646. sForwardProps[cfp++].Value.lpszW = (WCHAR*)strSubject.c_str();
  647. sForwardProps[cfp].ulPropTag = PR_SENTMAIL_ENTRYID;
  648. sForwardProps[cfp].Value.bin.cb = lpSentMailEntryID->Value.bin.cb;
  649. sForwardProps[cfp++].Value.bin.lpb = lpSentMailEntryID->Value.bin.lpb;
  650. if (bForwardAsAttachment) {
  651. sForwardProps[cfp].ulPropTag = PR_MESSAGE_CLASS;
  652. sForwardProps[cfp++].Value.lpszW = const_cast<wchar_t *>(L"IPM.Note");
  653. }
  654. if (parseBool(g_lpConfig->GetSetting("set_rule_headers", NULL, "yes"))) {
  655. PROPMAP_START(1)
  656. PROPMAP_NAMED_ID(KopanoRuleAction, PT_UNICODE, PS_INTERNET_HEADERS, "x-kopano-rule-action")
  657. PROPMAP_INIT(lpFwdMsg);
  658. sForwardProps[cfp].ulPropTag = PROP_KopanoRuleAction;
  659. sForwardProps[cfp++].Value.lpszW = LPWSTR(bDoPreserveSender ? L"redirect" : L"forward");
  660. }
  661. hr = lpFwdMsg->SetProps(cfp, sForwardProps, NULL);
  662. if (hr != hrSuccess)
  663. goto exitpm;
  664. if (!bDoNotMunge && !bForwardAsAttachment) {
  665. // because we're forwarding this as a new message, clear the old received message id
  666. static constexpr const SizedSPropTagArray(1, sptaDeleteProps) =
  667. {1, {PR_INTERNET_MESSAGE_ID}};
  668. hr = lpFwdMsg->DeleteProps(sptaDeleteProps, NULL);
  669. if(hr != hrSuccess)
  670. goto exitpm;
  671. MungeForwardBody(lpFwdMsg, lpOrigMessage);
  672. }
  673. *lppMessage = lpFwdMsg;
  674. exitpm:
  675. return hr;
  676. }
  677. // HRESULT HrDelegateMessage(LPMAPISESSION lpSession, LPEXCHANGEMANAGESTORE lpIEMS, IMAPIProp *lpMessage, LPADRENTRY lpAddress)
  678. static HRESULT HrDelegateMessage(IMAPIProp *lpMessage)
  679. {
  680. HRESULT hr = hrSuccess;
  681. SPropValue sNewProps[6] = {{0}};
  682. memory_ptr<SPropValue> lpProps;
  683. ULONG cValues = 0;
  684. static constexpr const SizedSPropTagArray(5, sptaRecipProps) =
  685. {5, {PR_RECEIVED_BY_ENTRYID, PR_RECEIVED_BY_ADDRTYPE,
  686. PR_RECEIVED_BY_EMAIL_ADDRESS, PR_RECEIVED_BY_NAME,
  687. PR_RECEIVED_BY_SEARCH_KEY}};
  688. static constexpr SizedSPropTagArray(1, sptaSentMail) =
  689. {1, {PR_SENTMAIL_ENTRYID}};
  690. // set PR_RCVD_REPRESENTING on original receiver
  691. hr = lpMessage->GetProps(sptaRecipProps, 0, &cValues, &~lpProps);
  692. if (hr != hrSuccess)
  693. return hr;
  694. lpProps[0].ulPropTag = PR_RCVD_REPRESENTING_ENTRYID;
  695. lpProps[1].ulPropTag = PR_RCVD_REPRESENTING_ADDRTYPE;
  696. lpProps[2].ulPropTag = PR_RCVD_REPRESENTING_EMAIL_ADDRESS;
  697. lpProps[3].ulPropTag = PR_RCVD_REPRESENTING_NAME;
  698. lpProps[4].ulPropTag = PR_RCVD_REPRESENTING_SEARCH_KEY;
  699. hr = lpMessage->SetProps(cValues, lpProps, NULL);
  700. if (hr != hrSuccess)
  701. return hr;
  702. // TODO: delete PR_RECEIVED_BY_ values?
  703. sNewProps[0].ulPropTag = PR_DELEGATED_BY_RULE;
  704. sNewProps[0].Value.b = TRUE;
  705. sNewProps[1].ulPropTag = PR_DELETE_AFTER_SUBMIT;
  706. sNewProps[1].Value.b = TRUE;
  707. hr = lpMessage->SetProps(2, sNewProps, NULL);
  708. if (hr != hrSuccess)
  709. return hr;
  710. // Don't want to move to sent mail
  711. hr = lpMessage->DeleteProps(sptaSentMail, NULL);
  712. if (hr != hrSuccess)
  713. return hr;
  714. return lpMessage->SaveChanges(KEEP_OPEN_READWRITE);
  715. }
  716. static int proc_op_fwd(IAddrBook *abook, IMsgStore *orig_store,
  717. const ACTION &act, const std::string &rule, StatsClient *sc,
  718. bool &bAddFwdFlag, IMessage **lppMessage)
  719. {
  720. object_ptr<IMessage> lpFwdMsg;
  721. HRESULT hr;
  722. sc->countInc("rules", "forward");
  723. // TODO: test act.lpAction[n].ulActionFlavor
  724. // FWD_PRESERVE_SENDER 1
  725. // FWD_DO_NOT_MUNGE_MSG 2
  726. // FWD_AS_ATTACHMENT 4
  727. // redirect == 3
  728. if (act.lpadrlist->cEntries == 0) {
  729. ec_log_debug("Forwarding rule doesn't have recipients");
  730. return 0; // Nothing todo
  731. }
  732. if (parseBool(g_lpConfig->GetSetting("no_double_forward"))) {
  733. /*
  734. * Loop protection. When header is added to the message, it
  735. * will stop to forward or redirect the message.
  736. */
  737. PROPMAP_START(1)
  738. PROPMAP_NAMED_ID(KopanoRuleAction, PT_UNICODE, PS_INTERNET_HEADERS, "x-kopano-rule-action")
  739. PROPMAP_INIT((*lppMessage));
  740. memory_ptr<SPropValue> lpPropRule;
  741. if (HrGetOneProp(*lppMessage, PROP_KopanoRuleAction, &~lpPropRule) == hrSuccess) {
  742. ec_log_warn((std::string)"Rule " + rule + ": FORWARD loop protection. Message will not be forwarded or redirected because it includes header \"x-kopano-rule-action\"");
  743. return 0;
  744. }
  745. }
  746. ec_log_debug("Rule action: %s e-mail", (act.ulActionFlavor & FWD_PRESERVE_SENDER) ? "redirecting" : "forwarding");
  747. hr = CreateForwardCopy(abook, orig_store, *lppMessage,
  748. act.lpadrlist, false, act.ulActionFlavor & FWD_PRESERVE_SENDER,
  749. act.ulActionFlavor & FWD_DO_NOT_MUNGE_MSG,
  750. act.ulActionFlavor & FWD_AS_ATTACHMENT, &~lpFwdMsg);
  751. if (hr != hrSuccess) {
  752. std::string msg = std::string("Rule ") + rule + ": FORWARD Unable to create forward message: %s (%x)";
  753. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  754. return hr == MAPI_E_NO_ACCESS ? -1 : 1;
  755. }
  756. hr = lpFwdMsg->SubmitMessage(0);
  757. if (hr != hrSuccess) {
  758. std::string msg = std::string("Rule ") + rule + ": FORWARD Unable to send forward message: %s (%x)";
  759. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  760. return 1;
  761. }
  762. // update original message, set as forwarded
  763. bAddFwdFlag = true;
  764. return 2;
  765. exitpm:
  766. return -1;
  767. }
  768. // lpMessage: gets EntryID, maybe pass this and close message in DAgent.cpp
  769. HRESULT HrProcessRules(const std::string &recip, pym_plugin_intf *pyMapiPlugin,
  770. IMAPISession *lpSession, IAddrBook *lpAdrBook, IMsgStore *lpOrigStore,
  771. IMAPIFolder *lpOrigInbox, IMessage **lppMessage, StatsClient *const sc)
  772. {
  773. HRESULT hr = hrSuccess;
  774. object_ptr<IExchangeModifyTable> lpTable;
  775. object_ptr<IMAPITable> lpView;
  776. LPMESSAGE lpTemplate = NULL;
  777. ULONG ulObjType;
  778. bool bAddFwdFlag = false;
  779. bool bMoved = false;
  780. static constexpr const SizedSPropTagArray(11, sptaRules) =
  781. {11, {PR_RULE_ID, PR_RULE_IDS, PR_RULE_SEQUENCE, PR_RULE_STATE,
  782. PR_RULE_USER_FLAGS, PR_RULE_CONDITION, PR_RULE_ACTIONS,
  783. PR_RULE_PROVIDER, CHANGE_PROP_TYPE(PR_RULE_NAME, PT_STRING8),
  784. PR_RULE_LEVEL, PR_RULE_PROVIDER_DATA}};
  785. static constexpr const SizedSSortOrderSet(1, sosRules) =
  786. {1, 0, 0, {{PR_RULE_SEQUENCE, TABLE_SORT_ASCEND}}};
  787. std::string strRule;
  788. LPSRestriction lpCondition = NULL;
  789. ACTIONS* lpActions = NULL;
  790. SPropValue sForwardProps[4];
  791. object_ptr<IECExchangeModifyTable> lpECModifyTable;
  792. ULONG ulResult= 0;
  793. sc -> countInc("rules", "invocations");
  794. hr = lpOrigInbox->OpenProperty(PR_RULES_TABLE, &IID_IExchangeModifyTable, 0, 0, &~lpTable);
  795. if (hr != hrSuccess) {
  796. ec_log_err("HrProcessRules(): OpenProperty failed %x", hr);
  797. goto exit;
  798. }
  799. hr = lpTable->QueryInterface(IID_IECExchangeModifyTable, &~lpECModifyTable);
  800. if(hr != hrSuccess) {
  801. ec_log_err("HrProcessRules(): QueryInterface failed %x", hr);
  802. goto exit;
  803. }
  804. hr = lpECModifyTable->DisablePushToServer();
  805. if(hr != hrSuccess) {
  806. ec_log_err("HrProcessRules(): DisablePushToServer failed %x", hr);
  807. goto exit;
  808. }
  809. hr = pyMapiPlugin->RulesProcessing("PreRuleProcess", lpSession, lpAdrBook, lpOrigStore, lpTable, &ulResult);
  810. if(hr != hrSuccess) {
  811. ec_log_err("HrProcessRules(): RulesProcessing failed %x", hr);
  812. goto exit;
  813. }
  814. //TODO do something with ulResults
  815. hr = lpTable->GetTable(0, &~lpView);
  816. if(hr != hrSuccess) {
  817. ec_log_err("HrProcessRules(): GetTable failed %x", hr);
  818. goto exit;
  819. }
  820. hr = lpView->SetColumns(sptaRules, 0);
  821. if (hr != hrSuccess) {
  822. ec_log_err("HrProcessRules(): SetColumns failed %x", hr);
  823. goto exit;
  824. }
  825. hr = lpView->SortTable(sosRules, 0);
  826. if (hr != hrSuccess) {
  827. ec_log_err("HrProcessRules(): SortTable failed %x", hr);
  828. goto exit;
  829. }
  830. while (1) {
  831. const SPropValue *lpProp = NULL;
  832. rowset_ptr lpRowSet;
  833. hr = lpView->QueryRows(1, 0, &~lpRowSet);
  834. if (hr != hrSuccess) {
  835. ec_log_err("HrProcessRules(): QueryRows failed %x", hr);
  836. goto exit;
  837. }
  838. if (lpRowSet->cRows == 0)
  839. break;
  840. sc -> countAdd("rules", "n_rules", int64_t(lpRowSet->cRows));
  841. auto lpRuleName = PCpropFindProp(lpRowSet->aRow[0].lpProps, lpRowSet->aRow[0].cValues, CHANGE_PROP_TYPE(PR_RULE_NAME, PT_STRING8));
  842. if (lpRuleName)
  843. strRule = lpRuleName->Value.lpszA;
  844. else
  845. strRule = "(no name)";
  846. ec_log_debug("Processing rule %s for %s", strRule.c_str(), recip.c_str());
  847. auto lpRuleState = PCpropFindProp(lpRowSet->aRow[0].lpProps, lpRowSet->aRow[0].cValues, PR_RULE_STATE);
  848. if (lpRuleState != nullptr && !(lpRuleState->Value.i & ST_ENABLED)) {
  849. ec_log_debug("Rule '%s' is disabled, skipping...", strRule.c_str());
  850. continue;
  851. }
  852. lpCondition = NULL;
  853. lpActions = NULL;
  854. lpProp = PCpropFindProp(lpRowSet->aRow[0].lpProps, lpRowSet->aRow[0].cValues, PR_RULE_CONDITION);
  855. if (lpProp)
  856. // NOTE: object is placed in Value.lpszA, not Value.x
  857. lpCondition = (LPSRestriction)lpProp->Value.lpszA;
  858. if (!lpCondition) {
  859. ec_log_debug("Rule '%s' has no contition, skipping...", strRule.c_str());
  860. continue;
  861. }
  862. lpProp = PCpropFindProp(lpRowSet->aRow[0].lpProps, lpRowSet->aRow[0].cValues, PR_RULE_ACTIONS);
  863. if (lpProp)
  864. // NOTE: object is placed in Value.lpszA, not Value.x
  865. lpActions = (ACTIONS*)lpProp->Value.lpszA;
  866. if (!lpActions) {
  867. ec_log_debug("Rule '%s' has no action, skipping...", strRule.c_str());
  868. continue;
  869. }
  870. // test if action should be done...
  871. // @todo: Create the correct locale for the current store.
  872. hr = TestRestriction(lpCondition, *lppMessage, createLocaleFromName(""));
  873. if (hr != hrSuccess) {
  874. ec_log_info("Rule %s doesn't match: 0x%08x", strRule.c_str(), hr);
  875. continue;
  876. }
  877. ec_log_info((std::string)"Rule " + strRule + " matches");
  878. sc -> countAdd("rules", "n_actions", int64_t(lpActions->cActions));
  879. for (ULONG n = 0; n < lpActions->cActions; ++n) {
  880. object_ptr<IMsgStore> lpDestStore;
  881. object_ptr<IMAPIFolder> lpDestFolder;
  882. object_ptr<IMessage> lpReplyMsg, lpFwdMsg, lpNewMessage;
  883. // do action
  884. switch(lpActions->lpAction[n].acttype) {
  885. case OP_MOVE:
  886. case OP_COPY:
  887. sc->countInc("rules", "copy_move");
  888. if (lpActions->lpAction[n].acttype == OP_COPY)
  889. ec_log_debug("Rule action: copying e-mail");
  890. else
  891. ec_log_debug("Rule action: moving e-mail");
  892. // First try to open the folder on the session as that will just work if we have the store open
  893. hr = lpSession->OpenEntry(lpActions->lpAction[n].actMoveCopy.cbFldEntryId,
  894. lpActions->lpAction[n].actMoveCopy.lpFldEntryId, &IID_IMAPIFolder, MAPI_MODIFY, &ulObjType,
  895. &~lpDestFolder);
  896. if (hr != hrSuccess) {
  897. std::string msg = std::string("Rule ") + strRule + ": Unable to open folder through session, trying through store: %s (%x)";
  898. ec_log_info(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  899. hr = lpSession->OpenMsgStore(0, lpActions->lpAction[n].actMoveCopy.cbStoreEntryId,
  900. lpActions->lpAction[n].actMoveCopy.lpStoreEntryId, nullptr, MAPI_BEST_ACCESS, &~lpDestStore);
  901. if (hr != hrSuccess) {
  902. std::string msg = std::string("Rule ") + strRule + ": Unable to open destination store: %s (%x)";
  903. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  904. continue;
  905. }
  906. hr = lpDestStore->OpenEntry(lpActions->lpAction[n].actMoveCopy.cbFldEntryId,
  907. lpActions->lpAction[n].actMoveCopy.lpFldEntryId, &IID_IMAPIFolder, MAPI_MODIFY, &ulObjType,
  908. &~lpDestFolder);
  909. if (hr != hrSuccess || ulObjType != MAPI_FOLDER) {
  910. std::string msg = std::string("Rule ") + strRule + ": Unable to open destination folder: %s (%x)";
  911. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  912. continue;
  913. }
  914. }
  915. hr = lpDestFolder->CreateMessage(nullptr, 0, &~lpNewMessage);
  916. if(hr != hrSuccess) {
  917. std::string msg = "Unable to create e-mail for rule " + strRule + ": %s (%x)";
  918. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  919. goto exit;
  920. }
  921. hr = (*lppMessage)->CopyTo(0, NULL, NULL, 0, NULL, &IID_IMessage, lpNewMessage, 0, NULL);
  922. if(hr != hrSuccess) {
  923. std::string msg = "Unable to copy e-mail for rule " + strRule + ": %s (%x)";
  924. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  925. goto exit;
  926. }
  927. hr = Util::HrCopyIMAPData((*lppMessage), lpNewMessage);
  928. // the function only returns errors on get/setprops, not when the data is just missing
  929. if (hr != hrSuccess) {
  930. std::string msg = "Unable to copy IMAP data e-mail for rule " + strRule + ", continuing: %s (%x)";
  931. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  932. hr = hrSuccess;
  933. goto exit;
  934. }
  935. // Save the copy in its new location
  936. hr = lpNewMessage->SaveChanges(0);
  937. if (hr != hrSuccess) {
  938. std::string msg = std::string("Rule ") + strRule + ": Unable to copy/move message: %s (%x)";
  939. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  940. continue;
  941. }
  942. if (lpActions->lpAction[n].acttype == OP_MOVE)
  943. bMoved = true;
  944. break;
  945. // may become dam's, may become normal rules (ol2k3)
  946. case OP_REPLY:
  947. case OP_OOF_REPLY:
  948. sc->countInc("rules", "reply_and_oof");
  949. if (lpActions->lpAction[n].acttype == OP_REPLY)
  950. ec_log_debug("Rule action: replying e-mail");
  951. else
  952. ec_log_debug("Rule action: OOF replying e-mail");
  953. hr = lpOrigInbox->OpenEntry(lpActions->lpAction[n].actReply.cbEntryId,
  954. lpActions->lpAction[n].actReply.lpEntryId, &IID_IMessage, 0, &ulObjType,
  955. (IUnknown**)&lpTemplate);
  956. if (hr != hrSuccess) {
  957. std::string msg = std::string("Rule ") + strRule + ": Unable to open reply message: %s (%x)";
  958. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  959. continue;
  960. }
  961. hr = CreateReplyCopy(lpSession, lpOrigStore, *lppMessage, lpTemplate, &~lpReplyMsg);
  962. if (hr != hrSuccess) {
  963. std::string msg = std::string("Rule ") + strRule + ": Unable to create reply message: %s (%x)";
  964. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  965. continue;
  966. }
  967. hr = lpReplyMsg->SubmitMessage(0);
  968. if (hr != hrSuccess) {
  969. std::string msg = std::string("Rule ") + strRule + ": Unable to send reply message: %s (%x)";
  970. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  971. continue;
  972. }
  973. break;
  974. case OP_FORWARD: {
  975. auto ret = proc_op_fwd(lpAdrBook, lpOrigStore, lpActions->lpAction[n], strRule, sc, bAddFwdFlag, lppMessage);
  976. if (ret == -1)
  977. goto exit;
  978. else if (ret == 0)
  979. continue;
  980. else if (ret == 1)
  981. continue;
  982. break;
  983. }
  984. case OP_BOUNCE:
  985. sc -> countInc("rules", "bounce");
  986. // scBounceCode?
  987. // TODO:
  988. // 1. make copy of lpMessage, needs CopyTo() function
  989. // 2. copy From: to To:
  990. // 3. SubmitMessage()
  991. ec_log_warn((std::string)"Rule "+strRule+": BOUNCE actions are currently unsupported");
  992. break;
  993. case OP_DELEGATE:
  994. sc -> countInc("rules", "delegate");
  995. if (lpActions->lpAction[n].lpadrlist->cEntries == 0) {
  996. ec_log_debug("Delegating rule doesn't have recipients");
  997. continue; // Nothing todo
  998. }
  999. ec_log_debug("Rule action: delegating e-mail");
  1000. hr = CreateForwardCopy(lpAdrBook, lpOrigStore, *lppMessage, lpActions->lpAction[n].lpadrlist, true, true, true, false, &~lpFwdMsg);
  1001. if (hr != hrSuccess) {
  1002. std::string msg = std::string("Rule ") + strRule + ": DELEGATE Unable to create delegate message: %s (%x)";
  1003. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  1004. continue;
  1005. }
  1006. // set delegate properties
  1007. hr = HrDelegateMessage(lpFwdMsg);
  1008. if (hr != hrSuccess) {
  1009. std::string msg = std::string("Rule ") + strRule + ": DELEGATE Unable to modify delegate message: %s (%x)";
  1010. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  1011. continue;
  1012. }
  1013. hr = lpFwdMsg->SubmitMessage(0);
  1014. if (hr != hrSuccess) {
  1015. std::string msg = std::string("Rule ") + strRule + ": DELEGATE Unable to send delegate message: %s (%x)";
  1016. ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
  1017. continue;
  1018. }
  1019. // don't set forwarded flag
  1020. break;
  1021. // will become a DAM atm, so I won't even bother implementing these ...
  1022. case OP_DEFER_ACTION:
  1023. sc -> countInc("rules", "defer");
  1024. // DAM crud, but outlook doesn't check these messages... yet
  1025. ec_log_warn((std::string)"Rule "+strRule+": DEFER client actions are currently unsupported");
  1026. break;
  1027. case OP_TAG:
  1028. sc -> countInc("rules", "tag");
  1029. // sure. WHEN YOU STOP WITH THE FRIGGIN' DEFER ACTION MESSAGES!!
  1030. ec_log_warn((std::string)"Rule "+strRule+": TAG actions are currently unsupported");
  1031. break;
  1032. case OP_DELETE:
  1033. sc -> countInc("rules", "delete");
  1034. // since *lppMessage wasn't yet saved in the server, we can just return a special MAPI Error code here,
  1035. // this will trigger the out-of-office mail (according to microsoft), but not save the message and drop it.
  1036. // The error code will become hrSuccess automatically after returning from the post processing function.
  1037. ec_log_debug("Rule action: deleting e-mail");
  1038. hr = MAPI_E_CANCEL;
  1039. goto exit;
  1040. break;
  1041. case OP_MARK_AS_READ:
  1042. sc -> countInc("rules", "mark_read");
  1043. // add prop read
  1044. ec_log_warn((std::string)"Rule "+strRule+": MARK AS READ actions are currently unsupported");
  1045. break;
  1046. };
  1047. } // end action loop
  1048. if (lpRuleState && (lpRuleState->Value.i & ST_EXIT_LEVEL))
  1049. break;
  1050. }
  1051. if (bAddFwdFlag) {
  1052. sForwardProps[0].ulPropTag = PR_ICON_INDEX;
  1053. sForwardProps[0].Value.ul = ICON_MAIL_FORWARDED;
  1054. sForwardProps[1].ulPropTag = PR_LAST_VERB_EXECUTED;
  1055. sForwardProps[1].Value.ul = NOTEIVERB_FORWARD;
  1056. sForwardProps[2].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
  1057. GetSystemTimeAsFileTime(&sForwardProps[2].Value.ft);
  1058. // set forward in msg flag
  1059. hr = (*lppMessage)->SetProps(3, sForwardProps, NULL);
  1060. }
  1061. exit:
  1062. if (hr != hrSuccess && hr != MAPI_E_CANCEL)
  1063. ec_log_info("Error while processing rules: 0x%08X", hr);
  1064. // The message was moved to another folder(s), do not save it in the inbox anymore, so cancel it.
  1065. if (hr == hrSuccess && bMoved)
  1066. hr = MAPI_E_CANCEL;
  1067. if (hr != hrSuccess)
  1068. sc -> countInc("rules", "invocations_fail");
  1069. return hr;
  1070. }