12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217 |
- /*
- * Copyright 2005 - 2016 Zarafa and its licensors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- #include <kopano/platform.h>
- #include "rules.h"
- #include <mapi.h>
- #include <mapidefs.h>
- #include <mapicode.h>
- #include <mapiutil.h>
- #include <kopano/mapiext.h>
- #include <kopano/memory.hpp>
- #include <edkmdb.h>
- #include <edkguid.h>
- #include <kopano/ECGetText.h>
- #include <kopano/stringutil.h>
- #include <kopano/Util.h>
- #include <kopano/CommonUtil.h>
- #include <kopano/ECLogger.h>
- #include <kopano/MAPIErrors.h>
- #include <kopano/mapi_ptr.h>
- #include <kopano/mapiguidext.h>
- #include <kopano/charset/convert.h>
- #include <kopano/hl.hpp>
- #include "IECExchangeModifyTable.h"
- #include "PyMapiPlugin.h"
- #include "spmain.h"
- using namespace std;
- using namespace KCHL;
- static HRESULT GetRecipStrings(LPMESSAGE lpMessage, std::wstring &wstrTo,
- std::wstring &wstrCc, std::wstring &wstrBcc)
- {
- SRowSetPtr ptrRows;
- MAPITablePtr ptrRecips;
- static constexpr const SizedSPropTagArray(2, sptaDisplay) =
- {2, {PR_DISPLAY_NAME_W, PR_RECIPIENT_TYPE}};
-
- wstrTo.clear();
- wstrCc.clear();
- wstrBcc.clear();
-
- HRESULT hr = lpMessage->GetRecipientTable(MAPI_UNICODE, &~ptrRecips);
- if(hr != hrSuccess)
- return hr;
- hr = ptrRecips->SetColumns(sptaDisplay, TBL_BATCH);
- if(hr != hrSuccess)
- return hr;
-
- while(1) {
- hr = ptrRecips->QueryRows(1, 0, &ptrRows);
- if(hr != hrSuccess)
- return hr;
-
- if(ptrRows.size() == 0)
- break;
-
- if(ptrRows[0].lpProps[0].ulPropTag != PR_DISPLAY_NAME_W || ptrRows[0].lpProps[1].ulPropTag != PR_RECIPIENT_TYPE)
- continue;
-
- switch(ptrRows[0].lpProps[1].Value.ul) {
- case MAPI_TO:
- if (!wstrTo.empty()) wstrTo += L"; ";
- wstrTo += ptrRows[0].lpProps[0].Value.lpszW;
- break;
- case MAPI_CC:
- if (!wstrCc.empty()) wstrCc += L"; ";
- wstrCc += ptrRows[0].lpProps[0].Value.lpszW;
- break;
- case MAPI_BCC:
- if (!wstrBcc.empty()) wstrBcc += L"; ";
- wstrBcc += ptrRows[0].lpProps[0].Value.lpszW;
- break;
- }
- }
- return hrSuccess;
- }
- static HRESULT MungeForwardBody(LPMESSAGE lpMessage, LPMESSAGE lpOrigMessage)
- {
- SPropArrayPtr ptrBodies;
- static constexpr const SizedSPropTagArray(4, sBody) =
- {4, {PR_BODY_W, PR_HTML, PR_RTF_IN_SYNC, PR_INTERNET_CPID}};
- SPropArrayPtr ptrInfo;
- static constexpr const SizedSPropTagArray(4, sInfo) =
- {4, {PR_SENT_REPRESENTING_NAME_W,
- PR_SENT_REPRESENTING_EMAIL_ADDRESS_W, PR_MESSAGE_DELIVERY_TIME,
- PR_SUBJECT_W}};
- ULONG ulCharset;
- ULONG cValues;
- bool bPlain = false;
- SPropValue sNewBody;
- StreamPtr ptrStream;
- string strHTML;
- string strHTMLForwardText;
- wstring wstrBody;
- wstring strForwardText;
- wstring wstrTo, wstrCc, wstrBcc;
- HRESULT hr = lpOrigMessage->GetProps(sBody, 0, &cValues, &~ptrBodies);
- if (FAILED(hr))
- return hr;
-
- if (PROP_TYPE(ptrBodies[3].ulPropTag) != PT_ERROR)
- ulCharset = ptrBodies[3].Value.ul;
- else
- ulCharset = 20127; // US-ASCII
- if (PROP_TYPE(ptrBodies[0].ulPropTag) == PT_ERROR && PROP_TYPE(ptrBodies[1].ulPropTag) == PT_ERROR)
- // plain and html not found, check sync flag
- bPlain = (ptrBodies[2].Value.b == FALSE);
- else
- bPlain = PROP_TYPE(ptrBodies[1].ulPropTag) == PT_ERROR && ptrBodies[1].Value.err == MAPI_E_NOT_FOUND;
- sNewBody.ulPropTag = bPlain ? PR_BODY_W : PR_HTML;
- // From: <fullname>
- // Sent: <date>
- // To: <original To:>
- // Cc: <original Cc:>
- // Subject: <>
- // Auto forwarded by a rule
-
- hr = GetRecipStrings(lpOrigMessage, wstrTo, wstrCc, wstrBcc);
- if (FAILED(hr))
- return hr;
- hr = lpOrigMessage->GetProps(sInfo, 0, &cValues, &~ptrInfo);
- if (FAILED(hr))
- return hr;
- if (bPlain) {
- // Plain text body
- strForwardText = L"From: ";
- if (PROP_TYPE(ptrInfo[0].ulPropTag) != PT_ERROR)
- strForwardText += ptrInfo[0].Value.lpszW;
- else if (PROP_TYPE(ptrInfo[1].ulPropTag) != PT_ERROR)
- strForwardText += ptrInfo[1].Value.lpszW;
-
- if (PROP_TYPE(ptrInfo[1].ulPropTag) != PT_ERROR) {
- strForwardText += L" <";
- strForwardText += ptrInfo[1].Value.lpszW;
- strForwardText += L">";
- }
- strForwardText += L"\nSent: ";
- if (PROP_TYPE(ptrInfo[2].ulPropTag) != PT_ERROR) {
- WCHAR buffer[64];
- time_t t;
- struct tm date;
- FileTimeToUnixTime(ptrInfo[2].Value.ft, &t);
- localtime_r(&t, &date);
- wcsftime(buffer, ARRAY_SIZE(buffer), L"%c", &date);
- strForwardText += buffer;
- }
- strForwardText += L"\nTo: ";
- strForwardText += wstrTo;
- strForwardText += L"\nCc: ";
- strForwardText += wstrCc;
- strForwardText += L"\nSubject: ";
- if (PROP_TYPE(ptrInfo[3].ulPropTag) != PT_ERROR)
- strForwardText += ptrInfo[3].Value.lpszW;
- strForwardText += L"\nAuto forwarded by a rule\n\n";
- if (ptrBodies[0].ulPropTag == PT_ERROR) {
- hr = lpOrigMessage->OpenProperty(PR_BODY_W, &IID_IStream, 0, 0, &~ptrStream);
- if (hr == hrSuccess)
- hr = Util::HrStreamToString(ptrStream, wstrBody);
- // stream
- strForwardText.append(wstrBody);
- } else {
- strForwardText += ptrBodies[0].Value.lpszW;
- }
- sNewBody.Value.lpszW = (WCHAR*)strForwardText.c_str();
- }
- else {
- // HTML body (or rtf, but nuts to editing that!)
- string strFind("<body");
- const char* pos;
- hr = lpOrigMessage->OpenProperty(PR_HTML, &IID_IStream, 0, 0, &~ptrStream);
- if (hr == hrSuccess)
- hr = Util::HrStreamToString(ptrStream, strHTML);
- // icase <body> tag
- pos = str_ifind(strHTML.c_str(), strFind.c_str());
- pos = pos ? pos + strFind.length() : strHTML.c_str();
- // if body tag was not found, this will make it be placed after the first tag, probably <html>
- if ((pos == strHTML.c_str() && *pos == '<') || pos != strHTML.c_str()) {
- // not all html bodies start actually using tags, so only seek if we find a <, or if we found a body tag starting point.
- while (*pos && *pos != '>')
- ++pos;
- if (*pos == '>')
- ++pos;
- }
- strHTMLForwardText = "<b>From:</b> ";
- if (PROP_TYPE(ptrInfo[0].ulPropTag) != PT_ERROR)
- Util::HrTextToHtml(ptrInfo[0].Value.lpszW, strHTMLForwardText, ulCharset);
- else if (PROP_TYPE(ptrInfo[1].ulPropTag) != PT_ERROR)
- Util::HrTextToHtml(ptrInfo[1].Value.lpszW, strHTMLForwardText, ulCharset);
- if (PROP_TYPE(ptrInfo[1].ulPropTag) != PT_ERROR) {
- strHTMLForwardText += " <<a href=\"mailto:";
- Util::HrTextToHtml(ptrInfo[1].Value.lpszW, strHTMLForwardText, ulCharset);
- strHTMLForwardText += "\">";
- Util::HrTextToHtml(ptrInfo[1].Value.lpszW, strHTMLForwardText, ulCharset);
- strHTMLForwardText += "</a>>";
- }
- strHTMLForwardText += "<br><b>Sent:</b> ";
- if (PROP_TYPE(ptrInfo[2].ulPropTag) != PT_ERROR) {
- char buffer[32];
- time_t t;
- struct tm date;
- FileTimeToUnixTime(ptrInfo[2].Value.ft, &t);
- localtime_r(&t, &date);
- strftime(buffer, 32, "%c", &date);
- strHTMLForwardText += buffer;
- }
- strHTMLForwardText += "<br><b>To:</b> ";
- Util::HrTextToHtml(wstrTo.c_str(), strHTMLForwardText, ulCharset);
- strHTMLForwardText += "<br><b>Cc:</b> ";
- Util::HrTextToHtml(wstrCc.c_str(), strHTMLForwardText, ulCharset);
- strHTMLForwardText += "<br><b>Subject:</b> ";
- if (PROP_TYPE(ptrInfo[3].ulPropTag) != PT_ERROR)
- Util::HrTextToHtml(ptrInfo[3].Value.lpszW, strHTMLForwardText, ulCharset);
- strHTMLForwardText += "<br><b>Auto forwarded by a rule</b><br><hr><br>";
- strHTML.insert((pos - strHTML.c_str()), strHTMLForwardText);
- sNewBody.Value.bin.cb = strHTML.size();
- sNewBody.Value.bin.lpb = (BYTE*)strHTML.c_str();
- }
- // set new body with forward markers
- return lpMessage->SetProps(1, &sNewBody, NULL);
- }
- static HRESULT CreateOutboxMessage(LPMDB lpOrigStore, LPMESSAGE *lppMessage)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpOutbox;
- memory_ptr<SPropValue> lpOutboxEntryID;
- ULONG ulObjType = 0;
- hr = HrGetOneProp(lpOrigStore, PR_IPM_OUTBOX_ENTRYID, &~lpOutboxEntryID);
- if (hr != hrSuccess)
- return hr;
- hr = lpOrigStore->OpenEntry(lpOutboxEntryID->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpOutboxEntryID->Value.bin.lpb), nullptr, MAPI_MODIFY, &ulObjType, &~lpOutbox);
- if (hr != hrSuccess)
- return hr;
- return lpOutbox->CreateMessage(nullptr, 0, lppMessage);
- }
- static HRESULT CreateReplyCopy(LPMAPISESSION lpSession, LPMDB lpOrigStore,
- IMAPIProp *lpOrigMessage, LPMESSAGE lpTemplate, LPMESSAGE *lppMessage)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMessage> lpReplyMessage;
- memory_ptr<SPropValue> lpProp, lpFrom, lpReplyRecipient;
- memory_ptr<SPropValue> lpSentMailEntryID;
- std::wstring strwSubject;
- ULONG cValues = 0;
- SizedADRLIST(1, sRecip) = {0, {}};
- ULONG ulCmp = 0;
- static constexpr const SizedSPropTagArray(5, sFrom) =
- {5, {PR_RECEIVED_BY_ENTRYID, PR_RECEIVED_BY_NAME,
- PR_RECEIVED_BY_ADDRTYPE, PR_RECEIVED_BY_EMAIL_ADDRESS,
- PR_RECEIVED_BY_SEARCH_KEY}};
- static constexpr const SizedSPropTagArray(6, sReplyRecipient) =
- {6, {PR_SENDER_ENTRYID, PR_SENDER_NAME, PR_SENDER_ADDRTYPE,
- PR_SENDER_EMAIL_ADDRESS, PR_SENDER_SEARCH_KEY, PR_NULL}};
- hr = CreateOutboxMessage(lpOrigStore, &~lpReplyMessage);
- if (hr != hrSuccess)
- goto exitpm;
- hr = lpTemplate->CopyTo(0, NULL, NULL, 0, NULL, &IID_IMessage, lpReplyMessage, 0, NULL);
- if (hr != hrSuccess)
- goto exitpm;
- // set "sent mail" folder entryid for spooler
- hr = HrGetOneProp(lpOrigStore, PR_IPM_SENTMAIL_ENTRYID, &~lpSentMailEntryID);
- if (hr != hrSuccess)
- goto exitpm;
- lpSentMailEntryID->ulPropTag = PR_SENTMAIL_ENTRYID;
- hr = HrSetOneProp(lpReplyMessage, lpSentMailEntryID);
- if (hr != hrSuccess)
- goto exitpm;
- // set a sensible subject
- hr = HrGetOneProp(lpReplyMessage, PR_SUBJECT_W, &~lpProp);
- if (hr == hrSuccess && lpProp->Value.lpszW[0] == L'\0') {
- // Exchange: uses "BT: orig subject" if empty, or only subject from template.
- hr = HrGetOneProp(lpOrigMessage, PR_SUBJECT_W, &~lpProp);
- if (hr == hrSuccess) {
- strwSubject = wstring(L"BT: ") + lpProp->Value.lpszW;
- lpProp->Value.lpszW = (WCHAR*)strwSubject.c_str();
- hr = HrSetOneProp(lpReplyMessage, lpProp);
- if (hr != hrSuccess)
- goto exitpm;
- }
- }
- hr = HrGetOneProp(lpOrigMessage, PR_INTERNET_MESSAGE_ID, &~lpProp);
- if (hr == hrSuccess) {
- lpProp->ulPropTag = PR_IN_REPLY_TO_ID;
- hr = HrSetOneProp(lpReplyMessage, lpProp);
- if (hr != hrSuccess)
- goto exitpm;
- }
- // set From to self
- hr = lpOrigMessage->GetProps(sFrom, 0, &cValues, &~lpFrom);
- if (FAILED(hr))
- goto exitpm;
- lpFrom[0].ulPropTag = CHANGE_PROP_TYPE(PR_SENT_REPRESENTING_ENTRYID, PROP_TYPE(lpFrom[0].ulPropTag));
- lpFrom[1].ulPropTag = CHANGE_PROP_TYPE(PR_SENT_REPRESENTING_NAME, PROP_TYPE(lpFrom[1].ulPropTag));
- lpFrom[2].ulPropTag = CHANGE_PROP_TYPE(PR_SENT_REPRESENTING_ADDRTYPE, PROP_TYPE(lpFrom[2].ulPropTag));
- lpFrom[3].ulPropTag = CHANGE_PROP_TYPE(PR_SENT_REPRESENTING_EMAIL_ADDRESS, PROP_TYPE(lpFrom[3].ulPropTag));
- lpFrom[4].ulPropTag = CHANGE_PROP_TYPE(PR_SENT_REPRESENTING_SEARCH_KEY, PROP_TYPE(lpFrom[4].ulPropTag));
- hr = lpReplyMessage->SetProps(5, lpFrom, NULL);
- if (FAILED(hr))
- goto exitpm;
- if (parseBool(g_lpConfig->GetSetting("set_rule_headers", NULL, "yes"))) {
- SPropValue sPropVal;
- PROPMAP_START(1)
- PROPMAP_NAMED_ID(KopanoRuleAction, PT_UNICODE, PS_INTERNET_HEADERS, "x-kopano-rule-action")
- PROPMAP_INIT(lpReplyMessage);
- sPropVal.ulPropTag = PROP_KopanoRuleAction;
- sPropVal.Value.lpszW = const_cast<wchar_t *>(L"reply");
- hr = HrSetOneProp(lpReplyMessage, &sPropVal);
- if (hr != hrSuccess)
- goto exitpm;
- }
- // append To with original sender
- // @todo get Reply-To ?
- hr = lpOrigMessage->GetProps(sReplyRecipient, 0, &cValues, &~lpReplyRecipient);
- if (FAILED(hr))
- goto exitpm;
- // obvious loop is being obvious
- if (PROP_TYPE(lpReplyRecipient[0].ulPropTag) != PT_ERROR && PROP_TYPE(lpFrom[0].ulPropTag ) != PT_ERROR) {
- hr = lpSession->CompareEntryIDs(lpReplyRecipient[0].Value.bin.cb, (LPENTRYID)lpReplyRecipient[0].Value.bin.lpb,
- lpFrom[0].Value.bin.cb, (LPENTRYID)lpFrom[0].Value.bin.lpb, 0, &ulCmp);
- if (hr == hrSuccess && ulCmp == TRUE) {
- hr = MAPI_E_UNABLE_TO_COMPLETE;
- goto exitpm;
- }
- }
- lpReplyRecipient[0].ulPropTag = CHANGE_PROP_TYPE(PR_ENTRYID, PROP_TYPE(lpReplyRecipient[0].ulPropTag));
- lpReplyRecipient[1].ulPropTag = CHANGE_PROP_TYPE(PR_DISPLAY_NAME, PROP_TYPE(lpReplyRecipient[1].ulPropTag));
- lpReplyRecipient[2].ulPropTag = CHANGE_PROP_TYPE(PR_ADDRTYPE, PROP_TYPE(lpReplyRecipient[2].ulPropTag));
- lpReplyRecipient[3].ulPropTag = CHANGE_PROP_TYPE(PR_EMAIL_ADDRESS, PROP_TYPE(lpReplyRecipient[3].ulPropTag));
- lpReplyRecipient[4].ulPropTag = CHANGE_PROP_TYPE(PR_SEARCH_KEY, PROP_TYPE(lpReplyRecipient[4].ulPropTag));
- lpReplyRecipient[5].ulPropTag = PR_RECIPIENT_TYPE;
- lpReplyRecipient[5].Value.ul = MAPI_TO;
- sRecip.cEntries = 1;
- sRecip.aEntries[0].cValues = cValues;
- sRecip.aEntries[0].rgPropVals = lpReplyRecipient;
- hr = lpReplyMessage->ModifyRecipients(MODRECIP_ADD, sRecip);
- if (FAILED(hr))
- goto exitpm;
- // return message
- hr = lpReplyMessage->QueryInterface(IID_IMessage, (void**)lppMessage);
- exitpm:
- return hr;
- }
- /**
- * @pat: pattern
- * @subj: subject string (domain part of an e-mail address)
- *
- * This _is_ different from kc_wildcard_cmp in that cmp2 allows
- * the asterisk to match dots.
- */
- static bool kc_wildcard_cmp2(const char *pat, const char *subj)
- {
- while (*pat != '\0' && *subj != '\0') {
- if (*pat == '*') {
- ++pat;
- for (; *subj != '\0'; ++subj)
- if (kc_wildcard_cmp2(pat, subj))
- return true;
- continue;
- }
- if (tolower(*pat) != tolower(*subj))
- return false;
- ++pat;
- ++subj;
- }
- return *pat == '\0' && *subj == '\0';
- }
- static bool proc_fwd_allowed(const std::vector<std::string> &wdomlist,
- const char *addr)
- {
- const char *p = strchr(addr, '@');
- if (p == nullptr) {
- ec_log_err("K-1900: Address \"%s\" had no '@', aborting forward because we could not possibly match it to a domain whitelist.", addr);
- return false;
- }
- addr = p + 1;
- for (const auto &wdomstr : wdomlist) {
- const char *wdom = wdomstr.c_str();
- if (strcmp(wdom, "*") == 0 /* fastpath */ ||
- kc_wildcard_cmp2(wdom, addr)) {
- ec_log_info("K-1901: proc_fwd_allowed: \"%s\" whitelist match on \"%s\"", addr, wdom);
- return true;
- }
- }
- ec_log_info("K-1902: proc_fwd_allowed: \"%s\" matched nothing on the whitelist", addr);
- return false;
- }
- /**
- * @inbox: folder to dump warning mail into
- * @addr: target address which delivery was rejected to
- *
- * Drop an additional message into @inbox informing about the
- * unwillingness to process a forwarding rule.
- */
- static HRESULT kc_send_fwdabort_notice(KCHL::KStore &&store, const char *addr)
- {
- using namespace KCHL;
- KMessage msg;
- try {
- KFolder inbox = store.open_entry(store.get_receive_folder(), nullptr, MAPI_MODIFY);
- msg = inbox.create_message(nullptr, 0);
- } catch (KCHL::KMAPIError &err) {
- ec_log_warn("K-2382: create_message: 0x%08x %s", err.code(), err.what());
- return err.code();
- }
- SPropValue prop[7];
- size_t nprop = 0;
- prop[nprop].ulPropTag = PR_SENDER_NAME_W;
- prop[nprop++].Value.lpszW = const_cast<wchar_t *>(L"Mail Delivery System");
- prop[nprop].ulPropTag = PR_SUBJECT_W;
- prop[nprop++].Value.lpszW = const_cast<wchar_t *>(L"Forwarding rule has been abrogated");
- prop[nprop].ulPropTag = PR_MESSAGE_FLAGS;
- prop[nprop++].Value.ul = 0;
- prop[nprop].ulPropTag = PR_MESSAGE_CLASS_W;
- prop[nprop++].Value.lpszW = const_cast<wchar_t *>(L"REPORT.IPM.Note.NDR");
- FILETIME ft;
- GetSystemTimeAsFileTime(&ft);
- prop[nprop].ulPropTag = PR_CLIENT_SUBMIT_TIME;
- prop[nprop++].Value.ft = ft;
- prop[nprop].ulPropTag = PR_MESSAGE_DELIVERY_TIME;
- prop[nprop++].Value.ft = ft;
- std::wstring newbody = convert_to<std::wstring>(format(_A(
- "The Kopano mail system is rejecting your request "
- "to forward e-mails (via mail filters) to <%s>: "
- "the operation is not permitted.\n\n"), addr) +
- _A("Remove the rule or contact your administrator about the "
- "\"forward_whitelist_domains\" setting.\n"));
- prop[nprop].ulPropTag = PR_BODY_W;
- prop[nprop++].Value.lpszW = const_cast<wchar_t *>(newbody.c_str());
- auto hr = msg->SetProps(nprop, prop, nullptr);
- if (hr != hrSuccess) {
- ec_log_err("K-3283: SetProps: 0x%08x %s", hr, GetMAPIErrorMessage(hr));
- return hr;
- }
- hr = msg->SaveChanges(KEEP_OPEN_READONLY);
- if (hr != hrSuccess) {
- ec_log_err("K-3284: commit: 0x%08x %s", hr, GetMAPIErrorMessage(hr));
- return hr;
- }
- hr = HrNewMailNotification(store, msg);
- if (hr != hrSuccess)
- ec_log_warn("K-3285: NewMailNotification: 0x%08x %s", hr, GetMAPIErrorMessage(hr));
- return hr;
- }
- /**
- * Checks the rule recipient list for a possible loop, and filters
- * that recipient. Returns an error when no recipients are left after
- * the filter.
- *
- * @param[in] lpMessage The original delivered message performing the rule action
- * @param[in] lpRuleRecipients The recipient list from the rule
- * @param[in] bOpDelegate If the action a delegate or forward action
- * @param[out] lppNewRecipients The actual recipient list to perform the action on
- *
- * @return MAPI error code
- */
- static HRESULT CheckRecipients(IAddrBook *lpAdrBook, IMsgStore *orig_store,
- IMAPIProp *lpMessage, const ADRLIST *lpRuleRecipients, bool bOpDelegate,
- bool bIncludeAsP1, ADRLIST **lppNewRecipients)
- {
- HRESULT hr = hrSuccess;
- adrlist_ptr lpRecipients;
- memory_ptr<SPropValue> lpMsgClass;
- std::wstring strFromName, strFromType, strFromAddress;
- std::wstring strRuleName, strRuleType, strRuleAddress;
- hr = HrGetAddress(lpAdrBook, (IMessage*)lpMessage,
- PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME_W,
- PR_SENT_REPRESENTING_ADDRTYPE_W, PR_SENT_REPRESENTING_EMAIL_ADDRESS_W,
- strFromName, strFromType, strFromAddress);
- if (hr != hrSuccess) {
- ec_log_err("Unable to get from address 0x%08X", hr);
- return hr;
- }
- hr = MAPIAllocateBuffer(CbNewADRLIST(lpRuleRecipients->cEntries), &~lpRecipients);
- if (hr != hrSuccess) {
- ec_log_err("CheckRecipients(): MAPIAllocateBuffer failed %x", hr);
- return hr;
- }
- std::vector<std::string> fwd_whitelist =
- tokenize(g_lpConfig->GetSetting("forward_whitelist_domains"), " ");
- HrGetOneProp(lpMessage, PR_MESSAGE_CLASS_A, &~lpMsgClass); //ignore errors
- lpRecipients->cEntries = 0;
- for (ULONG i = 0; i < lpRuleRecipients->cEntries; ++i) {
- hr = HrGetAddress(lpAdrBook, lpRuleRecipients->aEntries[i].rgPropVals, lpRuleRecipients->aEntries[i].cValues, PR_ENTRYID,
- CHANGE_PROP_TYPE(PR_DISPLAY_NAME, PT_UNSPECIFIED), CHANGE_PROP_TYPE(PR_ADDRTYPE, PT_UNSPECIFIED), CHANGE_PROP_TYPE(PR_SMTP_ADDRESS, PT_UNSPECIFIED),
- strRuleName, strRuleType, strRuleAddress);
- if (hr != hrSuccess) {
- ec_log_err("Unable to get rule address 0x%08X", hr);
- return hr;
- }
- auto rule_addr_std = convert_to<std::string>(strRuleAddress);
- if (!proc_fwd_allowed(fwd_whitelist, rule_addr_std.c_str())) {
- kc_send_fwdabort_notice(orig_store, rule_addr_std.c_str());
- return MAPI_E_NO_ACCESS;
- }
- if (strFromAddress == strRuleAddress &&
- // Hack for Meeting requests
- (!bOpDelegate || lpMsgClass == nullptr ||
- strstr(lpMsgClass->Value.lpszA, "IPM.Schedule.Meeting.") == nullptr)) {
- ec_log_info("Same user found in From and rule, blocking for loop protection");
- continue;
- }
- // copy recipient
- hr = Util::HrCopyPropertyArray(lpRuleRecipients->aEntries[i].rgPropVals, lpRuleRecipients->aEntries[i].cValues, &lpRecipients->aEntries[lpRecipients->cEntries].rgPropVals, &lpRecipients->aEntries[lpRecipients->cEntries].cValues, true);
- if (hr != hrSuccess) {
- ec_log_err("CheckRecipients(): Util::HrCopyPropertyArray failed %x", hr);
- return hr;
- }
- if(bIncludeAsP1) {
- auto lpRecipType = PpropFindProp(lpRecipients->aEntries[lpRecipients->cEntries].rgPropVals, lpRecipients->aEntries[lpRecipients->cEntries].cValues, PR_RECIPIENT_TYPE);
- if(!lpRecipType) {
- ec_log_crit("Attempt to add recipient with no PR_RECIPIENT_TYPE");
- return MAPI_E_INVALID_PARAMETER;
- }
-
- lpRecipType->Value.ul = MAPI_P1;
- }
- ++lpRecipients->cEntries;
- }
- if (lpRecipients->cEntries == 0) {
- ec_log_warn("Loop protection blocked all recipients, skipping rule");
- return MAPI_E_UNABLE_TO_COMPLETE;
- }
- if (lpRecipients->cEntries != lpRuleRecipients->cEntries)
- ec_log_info("Loop protection blocked some recipients");
- *lppNewRecipients = lpRecipients.release();
- lpRecipients = NULL;
- return hrSuccess;
- }
- static HRESULT CreateForwardCopy(IAddrBook *lpAdrBook, IMsgStore *lpOrigStore,
- IMessage *lpOrigMessage, const ADRLIST *lpRecipients,
- bool bOpDelegate, bool bDoPreserveSender, bool bDoNotMunge,
- bool bForwardAsAttachment, IMessage **lppMessage)
- {
- HRESULT hr = hrSuccess;
- LPMESSAGE lpFwdMsg = NULL;
- memory_ptr<SPropValue> lpSentMailEntryID, lpOrigSubject;
- memory_ptr<SPropTagArray> lpExclude;
- adrlist_ptr filtered_recips;
- ULONG ulANr = 0;
- static constexpr const SizedSPropTagArray(10, sExcludeFromCopyForward) = {10, {
- PR_TRANSPORT_MESSAGE_HEADERS,
- PR_SENT_REPRESENTING_ENTRYID,
- PR_SENT_REPRESENTING_NAME,
- PR_SENT_REPRESENTING_ADDRTYPE,
- PR_SENT_REPRESENTING_EMAIL_ADDRESS,
- PR_SENT_REPRESENTING_SEARCH_KEY,
- PR_READ_RECEIPT_REQUESTED,
- PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED,
- PR_MESSAGE_FLAGS,
- PR_MESSAGE_RECIPIENTS, // This must be the last entry, see bDoNotMunge
- } };
- static constexpr const SizedSPropTagArray (3, sExcludeFromCopyRedirect) = {3, {
- PR_TRANSPORT_MESSAGE_HEADERS,
- PR_MESSAGE_FLAGS,
- PR_MESSAGE_RECIPIENTS, // This must be the last entry, see bDoNotMunge
- } };
- static constexpr const SizedSPropTagArray(1, sExcludeFromAttachedForward) =
- {1, {PR_TRANSPORT_MESSAGE_HEADERS}};
- SPropValue sForwardProps[5];
- ULONG cfp = 0;
- wstring strSubject;
- if (lpRecipients == NULL || lpRecipients->cEntries == 0) {
- ec_log_crit("No rule recipient");
- hr = MAPI_E_INVALID_PARAMETER;
- goto exitpm;
- }
- hr = CheckRecipients(lpAdrBook, lpOrigStore, lpOrigMessage, lpRecipients,
- bOpDelegate, bDoNotMunge, &~filtered_recips);
- if (hr == MAPI_E_NO_ACCESS) {
- ec_log_info("K-1904: Forwarding not permitted. Ending rule processing.");
- goto exitpm;
- }
- if (hr == MAPI_E_UNABLE_TO_COMPLETE)
- goto exitpm;
- if (hr == hrSuccess)
- lpRecipients = filtered_recips.get();
- hr = HrGetOneProp(lpOrigStore, PR_IPM_SENTMAIL_ENTRYID, &~lpSentMailEntryID);
- if (hr != hrSuccess)
- goto exitpm;
- hr = CreateOutboxMessage(lpOrigStore, &lpFwdMsg);
- if (hr != hrSuccess)
- goto exitpm;
- // If we're doing a redirect, copy over the original PR_SENT_REPRESENTING_*, otherwise don't
- hr = Util::HrCopyPropTagArray(bDoPreserveSender ? sExcludeFromCopyRedirect : sExcludeFromCopyForward, &~lpExclude);
- if (hr != hrSuccess)
- return hr;
- if(bDoNotMunge) {
- // The idea here is to enable 'resend' mode and to include the original recipient list. What will
- // happen is that the original recipient list will be used to generate the headers of the message, but
- // only the MAPI_P1 recipients will be used to send the message to. This is exactly what we want. So
- // with bDoNotMunge, we copy the original recipient from the original message, and set MSGFLAG_RESEND.
-
- // Later on, we set the actual recipient to MAPI_P1
- SPropValue sPropResend;
- sPropResend.ulPropTag = PR_MESSAGE_FLAGS;
- sPropResend.Value.ul = MSGFLAG_UNSENT | MSGFLAG_RESEND | MSGFLAG_READ;
-
- --lpExclude->cValues; // strip PR_MESSAGE_RECIPIENTS, since original recipients should be used
- hr = HrSetOneProp(lpFwdMsg, &sPropResend);
- if(hr != hrSuccess)
- goto exitpm;
- }
- if (bForwardAsAttachment) {
- object_ptr<IAttach> lpAttach;
- object_ptr<IMessage> lpAttachMsg;
- hr = lpFwdMsg->CreateAttach(nullptr, 0, &ulANr, &~lpAttach);
- if (hr != hrSuccess)
- goto exitpm;
- SPropValue sAttachMethod;
- sAttachMethod.ulPropTag = PR_ATTACH_METHOD;
- sAttachMethod.Value.ul = ATTACH_EMBEDDED_MSG;
- hr = lpAttach->SetProps(1, &sAttachMethod, NULL);
- if (hr != hrSuccess)
- goto exitpm;
- hr = lpAttach->OpenProperty(PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, MAPI_CREATE | MAPI_MODIFY, &~lpAttachMsg);
- if (hr != hrSuccess)
- goto exitpm;
- hr = lpOrigMessage->CopyTo(0, NULL, sExcludeFromAttachedForward,
- 0, NULL, &IID_IMessage, lpAttachMsg, 0, NULL);
- if (hr != hrSuccess)
- goto exitpm;
- hr = lpAttachMsg->SaveChanges(0);
- if (hr != hrSuccess)
- goto exitpm;
- hr = lpAttach->SaveChanges(0);
- if (hr != hrSuccess)
- goto exitpm;
- }
- else {
- hr = lpOrigMessage->CopyTo(0, NULL, lpExclude, 0, NULL, &IID_IMessage, lpFwdMsg, 0, NULL);
- if (hr != hrSuccess)
- goto exitpm;
- }
- hr = lpFwdMsg->ModifyRecipients(MODRECIP_ADD, lpRecipients);
- if (hr != hrSuccess)
- goto exitpm;
- // set from email ??
- hr = HrGetOneProp(lpOrigMessage, PR_SUBJECT, &~lpOrigSubject);
- if (hr == hrSuccess)
- strSubject = lpOrigSubject->Value.lpszW;
- if(!bDoNotMunge || bForwardAsAttachment)
- strSubject.insert(0, L"FW: ");
- cfp = 0;
- sForwardProps[cfp].ulPropTag = PR_AUTO_FORWARDED;
- sForwardProps[cfp++].Value.b = TRUE;
- sForwardProps[cfp].ulPropTag = PR_SUBJECT;
- sForwardProps[cfp++].Value.lpszW = (WCHAR*)strSubject.c_str();
- sForwardProps[cfp].ulPropTag = PR_SENTMAIL_ENTRYID;
- sForwardProps[cfp].Value.bin.cb = lpSentMailEntryID->Value.bin.cb;
- sForwardProps[cfp++].Value.bin.lpb = lpSentMailEntryID->Value.bin.lpb;
- if (bForwardAsAttachment) {
- sForwardProps[cfp].ulPropTag = PR_MESSAGE_CLASS;
- sForwardProps[cfp++].Value.lpszW = const_cast<wchar_t *>(L"IPM.Note");
- }
- if (parseBool(g_lpConfig->GetSetting("set_rule_headers", NULL, "yes"))) {
- PROPMAP_START(1)
- PROPMAP_NAMED_ID(KopanoRuleAction, PT_UNICODE, PS_INTERNET_HEADERS, "x-kopano-rule-action")
- PROPMAP_INIT(lpFwdMsg);
- sForwardProps[cfp].ulPropTag = PROP_KopanoRuleAction;
- sForwardProps[cfp++].Value.lpszW = LPWSTR(bDoPreserveSender ? L"redirect" : L"forward");
- }
- hr = lpFwdMsg->SetProps(cfp, sForwardProps, NULL);
- if (hr != hrSuccess)
- goto exitpm;
- if (!bDoNotMunge && !bForwardAsAttachment) {
- // because we're forwarding this as a new message, clear the old received message id
- static constexpr const SizedSPropTagArray(1, sptaDeleteProps) =
- {1, {PR_INTERNET_MESSAGE_ID}};
- hr = lpFwdMsg->DeleteProps(sptaDeleteProps, NULL);
- if(hr != hrSuccess)
- goto exitpm;
- MungeForwardBody(lpFwdMsg, lpOrigMessage);
- }
- *lppMessage = lpFwdMsg;
- exitpm:
- return hr;
- }
- // HRESULT HrDelegateMessage(LPMAPISESSION lpSession, LPEXCHANGEMANAGESTORE lpIEMS, IMAPIProp *lpMessage, LPADRENTRY lpAddress)
- static HRESULT HrDelegateMessage(IMAPIProp *lpMessage)
- {
- HRESULT hr = hrSuccess;
- SPropValue sNewProps[6] = {{0}};
- memory_ptr<SPropValue> lpProps;
- ULONG cValues = 0;
- static constexpr const SizedSPropTagArray(5, sptaRecipProps) =
- {5, {PR_RECEIVED_BY_ENTRYID, PR_RECEIVED_BY_ADDRTYPE,
- PR_RECEIVED_BY_EMAIL_ADDRESS, PR_RECEIVED_BY_NAME,
- PR_RECEIVED_BY_SEARCH_KEY}};
- static constexpr SizedSPropTagArray(1, sptaSentMail) =
- {1, {PR_SENTMAIL_ENTRYID}};
- // set PR_RCVD_REPRESENTING on original receiver
- hr = lpMessage->GetProps(sptaRecipProps, 0, &cValues, &~lpProps);
- if (hr != hrSuccess)
- return hr;
- lpProps[0].ulPropTag = PR_RCVD_REPRESENTING_ENTRYID;
- lpProps[1].ulPropTag = PR_RCVD_REPRESENTING_ADDRTYPE;
- lpProps[2].ulPropTag = PR_RCVD_REPRESENTING_EMAIL_ADDRESS;
- lpProps[3].ulPropTag = PR_RCVD_REPRESENTING_NAME;
- lpProps[4].ulPropTag = PR_RCVD_REPRESENTING_SEARCH_KEY;
- hr = lpMessage->SetProps(cValues, lpProps, NULL);
- if (hr != hrSuccess)
- return hr;
- // TODO: delete PR_RECEIVED_BY_ values?
- sNewProps[0].ulPropTag = PR_DELEGATED_BY_RULE;
- sNewProps[0].Value.b = TRUE;
- sNewProps[1].ulPropTag = PR_DELETE_AFTER_SUBMIT;
- sNewProps[1].Value.b = TRUE;
- hr = lpMessage->SetProps(2, sNewProps, NULL);
- if (hr != hrSuccess)
- return hr;
-
- // Don't want to move to sent mail
- hr = lpMessage->DeleteProps(sptaSentMail, NULL);
- if (hr != hrSuccess)
- return hr;
- return lpMessage->SaveChanges(KEEP_OPEN_READWRITE);
- }
- static int proc_op_fwd(IAddrBook *abook, IMsgStore *orig_store,
- const ACTION &act, const std::string &rule, StatsClient *sc,
- bool &bAddFwdFlag, IMessage **lppMessage)
- {
- object_ptr<IMessage> lpFwdMsg;
- HRESULT hr;
- sc->countInc("rules", "forward");
- // TODO: test act.lpAction[n].ulActionFlavor
- // FWD_PRESERVE_SENDER 1
- // FWD_DO_NOT_MUNGE_MSG 2
- // FWD_AS_ATTACHMENT 4
- // redirect == 3
- if (act.lpadrlist->cEntries == 0) {
- ec_log_debug("Forwarding rule doesn't have recipients");
- return 0; // Nothing todo
- }
- if (parseBool(g_lpConfig->GetSetting("no_double_forward"))) {
- /*
- * Loop protection. When header is added to the message, it
- * will stop to forward or redirect the message.
- */
- PROPMAP_START(1)
- PROPMAP_NAMED_ID(KopanoRuleAction, PT_UNICODE, PS_INTERNET_HEADERS, "x-kopano-rule-action")
- PROPMAP_INIT((*lppMessage));
- memory_ptr<SPropValue> lpPropRule;
- if (HrGetOneProp(*lppMessage, PROP_KopanoRuleAction, &~lpPropRule) == hrSuccess) {
- 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\"");
- return 0;
- }
- }
- ec_log_debug("Rule action: %s e-mail", (act.ulActionFlavor & FWD_PRESERVE_SENDER) ? "redirecting" : "forwarding");
- hr = CreateForwardCopy(abook, orig_store, *lppMessage,
- act.lpadrlist, false, act.ulActionFlavor & FWD_PRESERVE_SENDER,
- act.ulActionFlavor & FWD_DO_NOT_MUNGE_MSG,
- act.ulActionFlavor & FWD_AS_ATTACHMENT, &~lpFwdMsg);
- if (hr != hrSuccess) {
- std::string msg = std::string("Rule ") + rule + ": FORWARD Unable to create forward message: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- return hr == MAPI_E_NO_ACCESS ? -1 : 1;
- }
- hr = lpFwdMsg->SubmitMessage(0);
- if (hr != hrSuccess) {
- std::string msg = std::string("Rule ") + rule + ": FORWARD Unable to send forward message: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- return 1;
- }
- // update original message, set as forwarded
- bAddFwdFlag = true;
- return 2;
- exitpm:
- return -1;
- }
- // lpMessage: gets EntryID, maybe pass this and close message in DAgent.cpp
- HRESULT HrProcessRules(const std::string &recip, pym_plugin_intf *pyMapiPlugin,
- IMAPISession *lpSession, IAddrBook *lpAdrBook, IMsgStore *lpOrigStore,
- IMAPIFolder *lpOrigInbox, IMessage **lppMessage, StatsClient *const sc)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IExchangeModifyTable> lpTable;
- object_ptr<IMAPITable> lpView;
- LPMESSAGE lpTemplate = NULL;
- ULONG ulObjType;
- bool bAddFwdFlag = false;
- bool bMoved = false;
- static constexpr const SizedSPropTagArray(11, sptaRules) =
- {11, {PR_RULE_ID, PR_RULE_IDS, PR_RULE_SEQUENCE, PR_RULE_STATE,
- PR_RULE_USER_FLAGS, PR_RULE_CONDITION, PR_RULE_ACTIONS,
- PR_RULE_PROVIDER, CHANGE_PROP_TYPE(PR_RULE_NAME, PT_STRING8),
- PR_RULE_LEVEL, PR_RULE_PROVIDER_DATA}};
- static constexpr const SizedSSortOrderSet(1, sosRules) =
- {1, 0, 0, {{PR_RULE_SEQUENCE, TABLE_SORT_ASCEND}}};
- std::string strRule;
- LPSRestriction lpCondition = NULL;
- ACTIONS* lpActions = NULL;
- SPropValue sForwardProps[4];
- object_ptr<IECExchangeModifyTable> lpECModifyTable;
- ULONG ulResult= 0;
- sc -> countInc("rules", "invocations");
- hr = lpOrigInbox->OpenProperty(PR_RULES_TABLE, &IID_IExchangeModifyTable, 0, 0, &~lpTable);
- if (hr != hrSuccess) {
- ec_log_err("HrProcessRules(): OpenProperty failed %x", hr);
- goto exit;
- }
- hr = lpTable->QueryInterface(IID_IECExchangeModifyTable, &~lpECModifyTable);
- if(hr != hrSuccess) {
- ec_log_err("HrProcessRules(): QueryInterface failed %x", hr);
- goto exit;
- }
- hr = lpECModifyTable->DisablePushToServer();
- if(hr != hrSuccess) {
- ec_log_err("HrProcessRules(): DisablePushToServer failed %x", hr);
- goto exit;
- }
- hr = pyMapiPlugin->RulesProcessing("PreRuleProcess", lpSession, lpAdrBook, lpOrigStore, lpTable, &ulResult);
- if(hr != hrSuccess) {
- ec_log_err("HrProcessRules(): RulesProcessing failed %x", hr);
- goto exit;
- }
- //TODO do something with ulResults
- hr = lpTable->GetTable(0, &~lpView);
- if(hr != hrSuccess) {
- ec_log_err("HrProcessRules(): GetTable failed %x", hr);
- goto exit;
- }
- hr = lpView->SetColumns(sptaRules, 0);
- if (hr != hrSuccess) {
- ec_log_err("HrProcessRules(): SetColumns failed %x", hr);
- goto exit;
- }
- hr = lpView->SortTable(sosRules, 0);
- if (hr != hrSuccess) {
- ec_log_err("HrProcessRules(): SortTable failed %x", hr);
- goto exit;
- }
- while (1) {
- const SPropValue *lpProp = NULL;
- rowset_ptr lpRowSet;
- hr = lpView->QueryRows(1, 0, &~lpRowSet);
- if (hr != hrSuccess) {
- ec_log_err("HrProcessRules(): QueryRows failed %x", hr);
- goto exit;
- }
- if (lpRowSet->cRows == 0)
- break;
- sc -> countAdd("rules", "n_rules", int64_t(lpRowSet->cRows));
- auto lpRuleName = PCpropFindProp(lpRowSet->aRow[0].lpProps, lpRowSet->aRow[0].cValues, CHANGE_PROP_TYPE(PR_RULE_NAME, PT_STRING8));
- if (lpRuleName)
- strRule = lpRuleName->Value.lpszA;
- else
- strRule = "(no name)";
- ec_log_debug("Processing rule %s for %s", strRule.c_str(), recip.c_str());
- auto lpRuleState = PCpropFindProp(lpRowSet->aRow[0].lpProps, lpRowSet->aRow[0].cValues, PR_RULE_STATE);
- if (lpRuleState != nullptr && !(lpRuleState->Value.i & ST_ENABLED)) {
- ec_log_debug("Rule '%s' is disabled, skipping...", strRule.c_str());
- continue;
- }
- lpCondition = NULL;
- lpActions = NULL;
- lpProp = PCpropFindProp(lpRowSet->aRow[0].lpProps, lpRowSet->aRow[0].cValues, PR_RULE_CONDITION);
- if (lpProp)
- // NOTE: object is placed in Value.lpszA, not Value.x
- lpCondition = (LPSRestriction)lpProp->Value.lpszA;
- if (!lpCondition) {
- ec_log_debug("Rule '%s' has no contition, skipping...", strRule.c_str());
- continue;
- }
- lpProp = PCpropFindProp(lpRowSet->aRow[0].lpProps, lpRowSet->aRow[0].cValues, PR_RULE_ACTIONS);
- if (lpProp)
- // NOTE: object is placed in Value.lpszA, not Value.x
- lpActions = (ACTIONS*)lpProp->Value.lpszA;
- if (!lpActions) {
- ec_log_debug("Rule '%s' has no action, skipping...", strRule.c_str());
- continue;
- }
-
- // test if action should be done...
- // @todo: Create the correct locale for the current store.
- hr = TestRestriction(lpCondition, *lppMessage, createLocaleFromName(""));
- if (hr != hrSuccess) {
- ec_log_info("Rule %s doesn't match: 0x%08x", strRule.c_str(), hr);
- continue;
- }
- ec_log_info((std::string)"Rule " + strRule + " matches");
- sc -> countAdd("rules", "n_actions", int64_t(lpActions->cActions));
- for (ULONG n = 0; n < lpActions->cActions; ++n) {
- object_ptr<IMsgStore> lpDestStore;
- object_ptr<IMAPIFolder> lpDestFolder;
- object_ptr<IMessage> lpReplyMsg, lpFwdMsg, lpNewMessage;
- // do action
- switch(lpActions->lpAction[n].acttype) {
- case OP_MOVE:
- case OP_COPY:
- sc->countInc("rules", "copy_move");
- if (lpActions->lpAction[n].acttype == OP_COPY)
- ec_log_debug("Rule action: copying e-mail");
- else
- ec_log_debug("Rule action: moving e-mail");
- // First try to open the folder on the session as that will just work if we have the store open
- hr = lpSession->OpenEntry(lpActions->lpAction[n].actMoveCopy.cbFldEntryId,
- lpActions->lpAction[n].actMoveCopy.lpFldEntryId, &IID_IMAPIFolder, MAPI_MODIFY, &ulObjType,
- &~lpDestFolder);
- if (hr != hrSuccess) {
- std::string msg = std::string("Rule ") + strRule + ": Unable to open folder through session, trying through store: %s (%x)";
- ec_log_info(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- hr = lpSession->OpenMsgStore(0, lpActions->lpAction[n].actMoveCopy.cbStoreEntryId,
- lpActions->lpAction[n].actMoveCopy.lpStoreEntryId, nullptr, MAPI_BEST_ACCESS, &~lpDestStore);
- if (hr != hrSuccess) {
- std::string msg = std::string("Rule ") + strRule + ": Unable to open destination store: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- continue;
- }
- hr = lpDestStore->OpenEntry(lpActions->lpAction[n].actMoveCopy.cbFldEntryId,
- lpActions->lpAction[n].actMoveCopy.lpFldEntryId, &IID_IMAPIFolder, MAPI_MODIFY, &ulObjType,
- &~lpDestFolder);
- if (hr != hrSuccess || ulObjType != MAPI_FOLDER) {
- std::string msg = std::string("Rule ") + strRule + ": Unable to open destination folder: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- continue;
- }
- }
- hr = lpDestFolder->CreateMessage(nullptr, 0, &~lpNewMessage);
- if(hr != hrSuccess) {
- std::string msg = "Unable to create e-mail for rule " + strRule + ": %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- goto exit;
- }
-
- hr = (*lppMessage)->CopyTo(0, NULL, NULL, 0, NULL, &IID_IMessage, lpNewMessage, 0, NULL);
- if(hr != hrSuccess) {
- std::string msg = "Unable to copy e-mail for rule " + strRule + ": %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- goto exit;
- }
- hr = Util::HrCopyIMAPData((*lppMessage), lpNewMessage);
- // the function only returns errors on get/setprops, not when the data is just missing
- if (hr != hrSuccess) {
- std::string msg = "Unable to copy IMAP data e-mail for rule " + strRule + ", continuing: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- hr = hrSuccess;
- goto exit;
- }
- // Save the copy in its new location
- hr = lpNewMessage->SaveChanges(0);
- if (hr != hrSuccess) {
- std::string msg = std::string("Rule ") + strRule + ": Unable to copy/move message: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- continue;
- }
- if (lpActions->lpAction[n].acttype == OP_MOVE)
- bMoved = true;
- break;
- // may become dam's, may become normal rules (ol2k3)
- case OP_REPLY:
- case OP_OOF_REPLY:
- sc->countInc("rules", "reply_and_oof");
- if (lpActions->lpAction[n].acttype == OP_REPLY)
- ec_log_debug("Rule action: replying e-mail");
- else
- ec_log_debug("Rule action: OOF replying e-mail");
- hr = lpOrigInbox->OpenEntry(lpActions->lpAction[n].actReply.cbEntryId,
- lpActions->lpAction[n].actReply.lpEntryId, &IID_IMessage, 0, &ulObjType,
- (IUnknown**)&lpTemplate);
- if (hr != hrSuccess) {
- std::string msg = std::string("Rule ") + strRule + ": Unable to open reply message: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- continue;
- }
- hr = CreateReplyCopy(lpSession, lpOrigStore, *lppMessage, lpTemplate, &~lpReplyMsg);
- if (hr != hrSuccess) {
- std::string msg = std::string("Rule ") + strRule + ": Unable to create reply message: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- continue;
- }
- hr = lpReplyMsg->SubmitMessage(0);
- if (hr != hrSuccess) {
- std::string msg = std::string("Rule ") + strRule + ": Unable to send reply message: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- continue;
- }
- break;
- case OP_FORWARD: {
- auto ret = proc_op_fwd(lpAdrBook, lpOrigStore, lpActions->lpAction[n], strRule, sc, bAddFwdFlag, lppMessage);
- if (ret == -1)
- goto exit;
- else if (ret == 0)
- continue;
- else if (ret == 1)
- continue;
- break;
- }
- case OP_BOUNCE:
- sc -> countInc("rules", "bounce");
- // scBounceCode?
- // TODO:
- // 1. make copy of lpMessage, needs CopyTo() function
- // 2. copy From: to To:
- // 3. SubmitMessage()
- ec_log_warn((std::string)"Rule "+strRule+": BOUNCE actions are currently unsupported");
- break;
- case OP_DELEGATE:
- sc -> countInc("rules", "delegate");
- if (lpActions->lpAction[n].lpadrlist->cEntries == 0) {
- ec_log_debug("Delegating rule doesn't have recipients");
- continue; // Nothing todo
- }
- ec_log_debug("Rule action: delegating e-mail");
- hr = CreateForwardCopy(lpAdrBook, lpOrigStore, *lppMessage, lpActions->lpAction[n].lpadrlist, true, true, true, false, &~lpFwdMsg);
- if (hr != hrSuccess) {
- std::string msg = std::string("Rule ") + strRule + ": DELEGATE Unable to create delegate message: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- continue;
- }
- // set delegate properties
- hr = HrDelegateMessage(lpFwdMsg);
- if (hr != hrSuccess) {
- std::string msg = std::string("Rule ") + strRule + ": DELEGATE Unable to modify delegate message: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- continue;
- }
- hr = lpFwdMsg->SubmitMessage(0);
- if (hr != hrSuccess) {
- std::string msg = std::string("Rule ") + strRule + ": DELEGATE Unable to send delegate message: %s (%x)";
- ec_log_err(msg.c_str(), GetMAPIErrorMessage(hr), hr);
- continue;
- }
- // don't set forwarded flag
- break;
- // will become a DAM atm, so I won't even bother implementing these ...
- case OP_DEFER_ACTION:
- sc -> countInc("rules", "defer");
- // DAM crud, but outlook doesn't check these messages... yet
- ec_log_warn((std::string)"Rule "+strRule+": DEFER client actions are currently unsupported");
- break;
- case OP_TAG:
- sc -> countInc("rules", "tag");
- // sure. WHEN YOU STOP WITH THE FRIGGIN' DEFER ACTION MESSAGES!!
- ec_log_warn((std::string)"Rule "+strRule+": TAG actions are currently unsupported");
- break;
- case OP_DELETE:
- sc -> countInc("rules", "delete");
- // since *lppMessage wasn't yet saved in the server, we can just return a special MAPI Error code here,
- // this will trigger the out-of-office mail (according to microsoft), but not save the message and drop it.
- // The error code will become hrSuccess automatically after returning from the post processing function.
- ec_log_debug("Rule action: deleting e-mail");
- hr = MAPI_E_CANCEL;
- goto exit;
- break;
- case OP_MARK_AS_READ:
- sc -> countInc("rules", "mark_read");
- // add prop read
- ec_log_warn((std::string)"Rule "+strRule+": MARK AS READ actions are currently unsupported");
- break;
- };
- } // end action loop
- if (lpRuleState && (lpRuleState->Value.i & ST_EXIT_LEVEL))
- break;
- }
- if (bAddFwdFlag) {
- sForwardProps[0].ulPropTag = PR_ICON_INDEX;
- sForwardProps[0].Value.ul = ICON_MAIL_FORWARDED;
- sForwardProps[1].ulPropTag = PR_LAST_VERB_EXECUTED;
- sForwardProps[1].Value.ul = NOTEIVERB_FORWARD;
- sForwardProps[2].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
- GetSystemTimeAsFileTime(&sForwardProps[2].Value.ft);
- // set forward in msg flag
- hr = (*lppMessage)->SetProps(3, sForwardProps, NULL);
- }
- exit:
- if (hr != hrSuccess && hr != MAPI_E_CANCEL)
- ec_log_info("Error while processing rules: 0x%08X", hr);
- // The message was moved to another folder(s), do not save it in the inbox anymore, so cancel it.
- if (hr == hrSuccess && bMoved)
- hr = MAPI_E_CANCEL;
- if (hr != hrSuccess)
- sc -> countInc("rules", "invocations_fail");
- return hr;
- }
|