ECVMIMEUtils.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <kopano/zcdefs.h>
  18. #include <kopano/platform.h>
  19. #include <exception>
  20. #include <memory>
  21. #include <iostream>
  22. #include "ECVMIMEUtils.h"
  23. #include "MAPISMTPTransport.h"
  24. #include <kopano/CommonUtil.h>
  25. #include <kopano/ECLogger.h>
  26. #include <kopano/ECRestriction.h>
  27. #include <kopano/memory.hpp>
  28. #include <kopano/charset/convert.h>
  29. #include <kopano/stringutil.h>
  30. #include <mapi.h>
  31. #include <mapitags.h>
  32. #include <mapidefs.h>
  33. #include <mapiutil.h>
  34. #include <mapix.h>
  35. #include <kopano/mapiext.h>
  36. #include <kopano/EMSAbTag.h>
  37. #include <kopano/ECABEntryID.h>
  38. #include <kopano/mapi_ptr.h>
  39. #include <vmime/base.hpp>
  40. using namespace std;
  41. using namespace KCHL;
  42. namespace KC {
  43. class mapiTimeoutHandler : public vmime::net::timeoutHandler {
  44. public:
  45. virtual ~mapiTimeoutHandler(void) _kc_impdtor;
  46. // @todo add logging
  47. virtual bool isTimeOut() { return getTime() >= (m_last + 5*60); };
  48. virtual void resetTimeOut() { m_last = getTime(); };
  49. virtual bool handleTimeOut() { return false; };
  50. const unsigned int getTime() const {
  51. return vmime::platform::getHandler()->getUnixTime();
  52. }
  53. private:
  54. unsigned int m_last = 0;
  55. };
  56. class mapiTimeoutHandlerFactory : public vmime::net::timeoutHandlerFactory {
  57. public:
  58. vmime::shared_ptr<vmime::net::timeoutHandler> create(void)
  59. {
  60. return vmime::make_shared<mapiTimeoutHandler>();
  61. };
  62. };
  63. ECVMIMESender::ECVMIMESender(const std::string &host, int port) :
  64. ECSender(host, port)
  65. {
  66. }
  67. /**
  68. * Adds all the recipients from a table into the passed recipient list
  69. *
  70. * @param lpAdrBook Pointer to the addressbook for the user sending the message (important for multi-tenancy separation)
  71. * @param lpTable Table to read recipients from
  72. * @param recipients Reference to list of recipients to append to
  73. * @param setGroups Set of groups already processed, used for loop-detection in nested expansion
  74. * @param setRecips Set of recipients already processed, used for duplicate-recip detection
  75. * @param bAllowEveryone Allow sending to 'everyone'
  76. *
  77. * This function takes a MAPI table, reads all items from it, expands any groups and adds all expanded recipients into the passed
  78. * recipient table. Group expansion is recursive.
  79. */
  80. HRESULT ECVMIMESender::HrAddRecipsFromTable(LPADRBOOK lpAdrBook, IMAPITable *lpTable, vmime::mailboxList &recipients, std::set<std::wstring> &setGroups, std::set<std::wstring> &setRecips, bool bAllowEveryone, bool bAlwaysExpandDistributionList)
  81. {
  82. rowset_ptr lpRowSet;
  83. std::wstring strName, strEmail, strType;
  84. HRESULT hr = lpTable->QueryRows(-1, 0, &~lpRowSet);
  85. if (hr != hrSuccess)
  86. return hr;
  87. // Get all recipients from the group
  88. for (ULONG i = 0; i < lpRowSet->cRows; ++i) {
  89. auto lpPropObjectType = PCpropFindProp(lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_OBJECT_TYPE);
  90. // see if there's an e-mail address associated with the list
  91. // if that's the case, then we send to that address and not all individual recipients in that list
  92. bool bAddrFetchSuccess = HrGetAddress(lpAdrBook, lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_ENTRYID, PR_DISPLAY_NAME_W, PR_ADDRTYPE_W, PR_EMAIL_ADDRESS_W, strName, strType, strEmail) == hrSuccess && !strEmail.empty();
  93. bool bItemIsAUser = lpPropObjectType == NULL || lpPropObjectType->Value.ul == MAPI_MAILUSER;
  94. if (bAddrFetchSuccess && bItemIsAUser) {
  95. if (setRecips.find(strEmail) == setRecips.end()) {
  96. recipients.appendMailbox(vmime::make_shared<vmime::mailbox>(convert_to<string>(strEmail)));
  97. setRecips.insert(strEmail);
  98. ec_log_debug("RCPT TO: %ls", strEmail.c_str());
  99. }
  100. continue;
  101. }
  102. if (lpPropObjectType == nullptr ||
  103. lpPropObjectType->Value.ul != MAPI_DISTLIST)
  104. continue;
  105. // Group
  106. auto lpGroupName = PCpropFindProp(lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_EMAIL_ADDRESS_W);
  107. auto lpGroupEntryID = PCpropFindProp(lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_ENTRYID);
  108. if (lpGroupName == nullptr || lpGroupEntryID == nullptr)
  109. return MAPI_E_NOT_FOUND;
  110. if (bAllowEveryone == false) {
  111. bool bEveryone = false;
  112. if (EntryIdIsEveryone(lpGroupEntryID->Value.bin.cb, (LPENTRYID)lpGroupEntryID->Value.bin.lpb, &bEveryone) == hrSuccess && bEveryone) {
  113. ec_log_err("Denying send to Everyone");
  114. error = std::wstring(L"You are not allowed to send to the 'Everyone' group");
  115. return MAPI_E_NO_ACCESS;
  116. }
  117. }
  118. if (bAlwaysExpandDistributionList || !bAddrFetchSuccess || wcscasecmp(strType.c_str(), L"SMTP") != 0) {
  119. // Recursively expand all recipients in the group
  120. hr = HrExpandGroup(lpAdrBook, lpGroupName, lpGroupEntryID, recipients, setGroups, setRecips, bAllowEveryone);
  121. if (hr == MAPI_E_TOO_COMPLEX || hr == MAPI_E_INVALID_PARAMETER) {
  122. // ignore group nesting loop and non user/group types (eg. companies)
  123. hr = hrSuccess;
  124. } else if (hr != hrSuccess) {
  125. // eg. MAPI_E_NOT_FOUND
  126. ec_log_err("Error while expanding group. Group: %ls, error: 0x%08x", lpGroupName->Value.lpszW, hr);
  127. error = std::wstring(L"Error in group '") + lpGroupName->Value.lpszW + L"', unable to send e-mail";
  128. return hr;
  129. }
  130. } else if (setRecips.find(strEmail) == setRecips.end()) {
  131. recipients.appendMailbox(vmime::make_shared<vmime::mailbox>(convert_to<string>(strEmail)));
  132. setRecips.insert(strEmail);
  133. ec_log_debug("Sending to group-address %s instead of expanded list",
  134. convert_to<std::string>(strEmail).c_str());
  135. }
  136. }
  137. return hr;
  138. }
  139. /**
  140. * Takes a group entry of a recipient table and expands the recipients for the group recursively by adding them to the recipients list
  141. *
  142. * @param lpAdrBook Pointer to the addressbook for the user sending the message (important for multi-tenancy separation)
  143. * @param lpGroupName Pointer to PR_EMAIL_ADDRESS_W entry for the recipient in the recipient table
  144. * @param lpGroupEntryId Pointer to PR_ENTRYID entry for the recipient in the recipient table
  145. * @param recipients Reference to list of VMIME recipients to be appended to
  146. * @param setGroups Set of already-processed groups (used for loop detecting in group expansion)
  147. * @param setRecips Set of recipients already processed, used for duplicate-recip detection
  148. *
  149. * This function expands the specified group by opening the group and adding all user entries to the recipients list, and
  150. * recursively expanding groups in the group.
  151. *
  152. * lpGroupEntryID may be NULL, in which case lpGroupName is used to resolve the group via the addressbook. If
  153. * both parameters are set, lpGroupEntryID is used, and lpGroupName is ignored.
  154. */
  155. HRESULT ECVMIMESender::HrExpandGroup(LPADRBOOK lpAdrBook,
  156. const SPropValue *lpGroupName, const SPropValue *lpGroupEntryID,
  157. vmime::mailboxList &recipients, std::set<std::wstring> &setGroups,
  158. std::set<std::wstring> &setRecips, bool bAllowEveryone)
  159. {
  160. HRESULT hr = hrSuccess;
  161. object_ptr<IDistList> lpGroup;
  162. ULONG ulType = 0;
  163. object_ptr<IMAPITable> lpTable;
  164. memory_ptr<SPropValue> lpEmailAddress;
  165. if (lpGroupEntryID == nullptr || lpAdrBook->OpenEntry(lpGroupEntryID->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpGroupEntryID->Value.bin.lpb), nullptr, 0, &ulType, &~lpGroup) != hrSuccess || ulType != MAPI_DISTLIST) {
  166. // Entry id for group was not given, or the group could not be opened, or the entryid was not a group (eg one-off entryid)
  167. // Therefore resolve group name, and open that instead.
  168. if (lpGroupName == nullptr)
  169. return MAPI_E_NOT_FOUND;
  170. rowset_ptr lpRows;
  171. hr = MAPIAllocateBuffer(sizeof(SRowSet), &~lpRows);
  172. if (hr != hrSuccess)
  173. return hr;
  174. lpRows->cRows = 1;
  175. if ((hr = MAPIAllocateBuffer(sizeof(SPropValue), (void **)&lpRows->aRow[0].lpProps)) != hrSuccess)
  176. return hr;
  177. lpRows->aRow[0].cValues = 1;
  178. lpRows->aRow[0].lpProps[0].ulPropTag = PR_DISPLAY_NAME_W;
  179. lpRows->aRow[0].lpProps[0].Value.lpszW = lpGroupName->Value.lpszW;
  180. hr = lpAdrBook->ResolveName(0, MAPI_UNICODE | EMS_AB_ADDRESS_LOOKUP, NULL, reinterpret_cast<ADRLIST *>(lpRows.get()));
  181. if(hr != hrSuccess)
  182. return hr;
  183. lpGroupEntryID = PCpropFindProp(lpRows->aRow[0].lpProps, lpRows->aRow[0].cValues, PR_ENTRYID);
  184. if (lpGroupEntryID == nullptr)
  185. return MAPI_E_NOT_FOUND;
  186. // Open resolved entry
  187. hr = lpAdrBook->OpenEntry(lpGroupEntryID->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpGroupEntryID->Value.bin.lpb), nullptr, 0, &ulType, &~lpGroup);
  188. if(hr != hrSuccess)
  189. return hr;
  190. if(ulType != MAPI_DISTLIST) {
  191. ec_log_debug("Expected group, but opened type %d", ulType);
  192. return MAPI_E_INVALID_PARAMETER;
  193. }
  194. }
  195. hr = HrGetOneProp(lpGroup, PR_EMAIL_ADDRESS_W, &~lpEmailAddress);
  196. if(hr != hrSuccess)
  197. return hr;
  198. if (setGroups.find(lpEmailAddress->Value.lpszW) != setGroups.end())
  199. // Group loops in nesting
  200. return MAPI_E_TOO_COMPLEX;
  201. // Add group name to list of processed groups
  202. setGroups.insert(lpEmailAddress->Value.lpszW);
  203. hr = lpGroup->GetContentsTable(MAPI_UNICODE, &~lpTable);
  204. if(hr != hrSuccess)
  205. return hr;
  206. return HrAddRecipsFromTable(lpAdrBook, lpTable, recipients, setGroups,
  207. setRecips, bAllowEveryone, true);
  208. }
  209. HRESULT ECVMIMESender::HrMakeRecipientsList(LPADRBOOK lpAdrBook,
  210. LPMESSAGE lpMessage, vmime::shared_ptr<vmime::message> vmMessage,
  211. vmime::mailboxList &recipients, bool bAllowEveryone,
  212. bool bAlwaysExpandDistrList)
  213. {
  214. HRESULT hr = hrSuccess;
  215. object_ptr<IMAPITable> lpRTable;
  216. bool bResend = false;
  217. std::set<std::wstring> setGroups; // Set of groups to make sure we don't get into an expansion-loop
  218. std::set<std::wstring> setRecips; // Set of recipients to make sure we don't send two identical RCPT TO's
  219. memory_ptr<SPropValue> lpMessageFlags;
  220. hr = HrGetOneProp(lpMessage, PR_MESSAGE_FLAGS, &~lpMessageFlags);
  221. if (hr != hrSuccess)
  222. return hr;
  223. if(lpMessageFlags->Value.ul & MSGFLAG_RESEND)
  224. bResend = true;
  225. hr = lpMessage->GetRecipientTable(MAPI_UNICODE, &~lpRTable);
  226. if (hr != hrSuccess)
  227. return hr;
  228. // When resending, only send to MAPI_P1 recipients
  229. if(bResend) {
  230. SPropValue sRestrictProp;
  231. sRestrictProp.ulPropTag = PR_RECIPIENT_TYPE;
  232. sRestrictProp.Value.ul = MAPI_P1;
  233. hr = ECPropertyRestriction(RELOP_EQ, PR_RECIPIENT_TYPE,
  234. &sRestrictProp, ECRestriction::Cheap)
  235. .RestrictTable(lpRTable, TBL_BATCH);
  236. if (hr != hrSuccess)
  237. return hr;
  238. }
  239. return HrAddRecipsFromTable(lpAdrBook, lpRTable, recipients, setGroups,
  240. setRecips, bAllowEveryone, bAlwaysExpandDistrList);
  241. }
  242. // This function does not catch the vmime exception
  243. // it should be handled by the calling party.
  244. HRESULT ECVMIMESender::sendMail(LPADRBOOK lpAdrBook, LPMESSAGE lpMessage,
  245. vmime::shared_ptr<vmime::message> vmMessage, bool bAllowEveryone,
  246. bool bAlwaysExpandDistrList)
  247. {
  248. HRESULT hr = hrSuccess;
  249. vmime::mailbox expeditor;
  250. vmime::mailboxList recipients;
  251. vmime::shared_ptr<vmime::messaging::session> vmSession;
  252. vmime::shared_ptr<vmime::messaging::transport> vmTransport;
  253. vmime::shared_ptr<vmime::net::smtp::MAPISMTPTransport> mapiTransport;
  254. if (lpMessage == NULL || vmMessage == NULL)
  255. return MAPI_E_INVALID_PARAMETER;
  256. smtpresult = 0;
  257. error.clear();
  258. try {
  259. // Session initialization (global properties)
  260. vmSession = vmime::net::session::create();
  261. // set the server address and port, plus type of service by use of url
  262. // and get our special mapismtp mailer
  263. vmime::utility::url url("mapismtp", smtphost, smtpport);
  264. vmTransport = vmSession->getTransport(url);
  265. vmTransport->setTimeoutHandlerFactory(vmime::make_shared<mapiTimeoutHandlerFactory>());
  266. // cast to access interface extra's
  267. mapiTransport = vmime::dynamicCast<vmime::net::smtp::MAPISMTPTransport>(vmTransport);
  268. // get expeditor for 'mail from:' smtp command
  269. if (vmMessage->getHeader()->hasField(vmime::fields::FROM))
  270. expeditor = *vmime::dynamicCast<vmime::mailbox>(vmMessage->getHeader()->findField(vmime::fields::FROM)->getValue());
  271. else
  272. throw vmime::exceptions::no_expeditor();
  273. if (expeditor.isEmpty()) {
  274. // cancel this message as unsendable, would otherwise be thrown out of transport::send()
  275. error = L"No expeditor in e-mail";
  276. return MAPI_W_CANCEL_MESSAGE;
  277. }
  278. hr = HrMakeRecipientsList(lpAdrBook, lpMessage, vmMessage, recipients, bAllowEveryone, bAlwaysExpandDistrList);
  279. if (hr != hrSuccess)
  280. return hr;
  281. if (recipients.isEmpty()) {
  282. // cancel this message as unsendable, would otherwise be thrown out of transport::send()
  283. error = L"No recipients in e-mail";
  284. return MAPI_W_CANCEL_MESSAGE;
  285. }
  286. // Remove BCC headers from the message we're about to send
  287. if (vmMessage->getHeader()->hasField(vmime::fields::BCC)) {
  288. auto bcc = vmMessage->getHeader()->findField(vmime::fields::BCC);
  289. vmMessage->getHeader()->removeField(bcc);
  290. }
  291. // Delivery report request
  292. SPropValuePtr ptrDeliveryReport;
  293. if (mapiTransport != nullptr &&
  294. HrGetOneProp(lpMessage, PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED, &~ptrDeliveryReport) == hrSuccess &&
  295. ptrDeliveryReport->Value.b == TRUE)
  296. mapiTransport->requestDSN(true, "");
  297. // Generate the message, "stream" it and delegate the sending
  298. // to the generic send() function.
  299. std::ostringstream oss;
  300. vmime::utility::outputStreamAdapter ossAdapter(oss);
  301. vmMessage->generate(ossAdapter);
  302. const string& str(oss.str()); // copy?
  303. vmime::utility::inputStreamStringAdapter isAdapter(str); // direct oss.str() ?
  304. // send the email already!
  305. bool ok = false;
  306. try {
  307. vmTransport->connect();
  308. } catch (vmime::exception &e) {
  309. // special error, smtp server not respoding, so try later again
  310. ec_log_err("Connect to SMTP: %s. E-Mail will be tried again later.", e.what());
  311. return MAPI_W_NO_SERVICE;
  312. }
  313. try {
  314. vmTransport->send(expeditor, recipients, isAdapter, str.length(), NULL);
  315. vmTransport->disconnect();
  316. ok = true;
  317. }
  318. catch (vmime::exceptions::command_error& e) {
  319. if (mapiTransport != NULL) {
  320. mPermanentFailedRecipients = mapiTransport->getPermanentFailedRecipients();
  321. mTemporaryFailedRecipients = mapiTransport->getTemporaryFailedRecipients();
  322. }
  323. ec_log_err("SMTP: %s Response: %s", e.what(), e.response().c_str());
  324. smtpresult = atoi(e.response().substr(0, e.response().find_first_of(" ")).c_str());
  325. error = convert_to<wstring>(e.response());
  326. // message should be cancelled, unsendable, test by smtp result code.
  327. return MAPI_W_CANCEL_MESSAGE;
  328. }
  329. catch (vmime::exceptions::no_recipient& e) {
  330. if (mapiTransport != NULL) {
  331. mPermanentFailedRecipients = mapiTransport->getPermanentFailedRecipients();
  332. mTemporaryFailedRecipients = mapiTransport->getTemporaryFailedRecipients();
  333. }
  334. ec_log_err("SMTP: %s Name: %s", e.what(), e.name());
  335. //smtpresult = atoi(e.response().substr(0, e.response().find_first_of(" ")).c_str());
  336. //error = convert_to<wstring>(e.response());
  337. // message should be cancelled, unsendable, test by smtp result code.
  338. return MAPI_W_CANCEL_MESSAGE;
  339. }
  340. catch (vmime::exception &e) {
  341. }
  342. if (mapiTransport != NULL) {
  343. /*
  344. * Multiple invalid recipients can cause the opponent
  345. * mail server (e.g. Postfix) to disconnect. In that
  346. * case, fail those recipients.
  347. */
  348. mPermanentFailedRecipients = mapiTransport->getPermanentFailedRecipients();
  349. mTemporaryFailedRecipients = mapiTransport->getTemporaryFailedRecipients();
  350. if (mPermanentFailedRecipients.size() == static_cast<size_t>(recipients.getMailboxCount())) {
  351. ec_log_err("SMTP: e-mail will be not be tried again: all recipients failed.");
  352. return MAPI_W_CANCEL_MESSAGE;
  353. } else if (!mTemporaryFailedRecipients.empty()) {
  354. ec_log_err("SMTP: e-mail will be tried again: some recipients failed.");
  355. return MAPI_W_PARTIAL_COMPLETION;
  356. } else if (!mPermanentFailedRecipients.empty()) {
  357. ec_log_err("SMTP: some recipients failed.");
  358. return MAPI_W_PARTIAL_COMPLETION;
  359. } else if (mTemporaryFailedRecipients.empty() && mPermanentFailedRecipients.empty() && !ok) {
  360. // special error, smtp server not respoding, so try later again
  361. ec_log_err("SMTP: e-mail will be tried again.");
  362. return MAPI_W_NO_SERVICE;
  363. }
  364. }
  365. }
  366. catch (vmime::exception& e) {
  367. // connection_greeting_error, ...?
  368. ec_log_err("%s", e.what());
  369. error = convert_to<wstring>(e.what());
  370. return MAPI_E_NETWORK_ERROR;
  371. }
  372. catch (std::exception& e) {
  373. ec_log_err("%s", e.what());
  374. error = convert_to<wstring>(e.what());
  375. return MAPI_E_NETWORK_ERROR;
  376. }
  377. return hr;
  378. }
  379. } /* namespace */