123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- /*
- * 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/zcdefs.h>
- #include <kopano/platform.h>
- #include <exception>
- #include <memory>
- #include <iostream>
- #include "ECVMIMEUtils.h"
- #include "MAPISMTPTransport.h"
- #include <kopano/CommonUtil.h>
- #include <kopano/ECLogger.h>
- #include <kopano/ECRestriction.h>
- #include <kopano/memory.hpp>
- #include <kopano/charset/convert.h>
- #include <kopano/stringutil.h>
- #include <mapi.h>
- #include <mapitags.h>
- #include <mapidefs.h>
- #include <mapiutil.h>
- #include <mapix.h>
- #include <kopano/mapiext.h>
- #include <kopano/EMSAbTag.h>
- #include <kopano/ECABEntryID.h>
- #include <kopano/mapi_ptr.h>
- #include <vmime/base.hpp>
- using namespace std;
- using namespace KCHL;
- namespace KC {
- class mapiTimeoutHandler : public vmime::net::timeoutHandler {
- public:
- virtual ~mapiTimeoutHandler(void) _kc_impdtor;
- // @todo add logging
- virtual bool isTimeOut() { return getTime() >= (m_last + 5*60); };
- virtual void resetTimeOut() { m_last = getTime(); };
- virtual bool handleTimeOut() { return false; };
- const unsigned int getTime() const {
- return vmime::platform::getHandler()->getUnixTime();
- }
- private:
- unsigned int m_last = 0;
- };
- class mapiTimeoutHandlerFactory : public vmime::net::timeoutHandlerFactory {
- public:
- vmime::shared_ptr<vmime::net::timeoutHandler> create(void)
- {
- return vmime::make_shared<mapiTimeoutHandler>();
- };
- };
- ECVMIMESender::ECVMIMESender(const std::string &host, int port) :
- ECSender(host, port)
- {
- }
- /**
- * Adds all the recipients from a table into the passed recipient list
- *
- * @param lpAdrBook Pointer to the addressbook for the user sending the message (important for multi-tenancy separation)
- * @param lpTable Table to read recipients from
- * @param recipients Reference to list of recipients to append to
- * @param setGroups Set of groups already processed, used for loop-detection in nested expansion
- * @param setRecips Set of recipients already processed, used for duplicate-recip detection
- * @param bAllowEveryone Allow sending to 'everyone'
- *
- * This function takes a MAPI table, reads all items from it, expands any groups and adds all expanded recipients into the passed
- * recipient table. Group expansion is recursive.
- */
- HRESULT ECVMIMESender::HrAddRecipsFromTable(LPADRBOOK lpAdrBook, IMAPITable *lpTable, vmime::mailboxList &recipients, std::set<std::wstring> &setGroups, std::set<std::wstring> &setRecips, bool bAllowEveryone, bool bAlwaysExpandDistributionList)
- {
- rowset_ptr lpRowSet;
- std::wstring strName, strEmail, strType;
- HRESULT hr = lpTable->QueryRows(-1, 0, &~lpRowSet);
- if (hr != hrSuccess)
- return hr;
- // Get all recipients from the group
- for (ULONG i = 0; i < lpRowSet->cRows; ++i) {
- auto lpPropObjectType = PCpropFindProp(lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_OBJECT_TYPE);
- // see if there's an e-mail address associated with the list
- // if that's the case, then we send to that address and not all individual recipients in that list
- 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();
- bool bItemIsAUser = lpPropObjectType == NULL || lpPropObjectType->Value.ul == MAPI_MAILUSER;
- if (bAddrFetchSuccess && bItemIsAUser) {
- if (setRecips.find(strEmail) == setRecips.end()) {
- recipients.appendMailbox(vmime::make_shared<vmime::mailbox>(convert_to<string>(strEmail)));
- setRecips.insert(strEmail);
- ec_log_debug("RCPT TO: %ls", strEmail.c_str());
- }
- continue;
- }
- if (lpPropObjectType == nullptr ||
- lpPropObjectType->Value.ul != MAPI_DISTLIST)
- continue;
- // Group
- auto lpGroupName = PCpropFindProp(lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_EMAIL_ADDRESS_W);
- auto lpGroupEntryID = PCpropFindProp(lpRowSet->aRow[i].lpProps, lpRowSet->aRow[i].cValues, PR_ENTRYID);
- if (lpGroupName == nullptr || lpGroupEntryID == nullptr)
- return MAPI_E_NOT_FOUND;
-
- if (bAllowEveryone == false) {
- bool bEveryone = false;
-
- if (EntryIdIsEveryone(lpGroupEntryID->Value.bin.cb, (LPENTRYID)lpGroupEntryID->Value.bin.lpb, &bEveryone) == hrSuccess && bEveryone) {
- ec_log_err("Denying send to Everyone");
- error = std::wstring(L"You are not allowed to send to the 'Everyone' group");
- return MAPI_E_NO_ACCESS;
- }
- }
- if (bAlwaysExpandDistributionList || !bAddrFetchSuccess || wcscasecmp(strType.c_str(), L"SMTP") != 0) {
- // Recursively expand all recipients in the group
- hr = HrExpandGroup(lpAdrBook, lpGroupName, lpGroupEntryID, recipients, setGroups, setRecips, bAllowEveryone);
- if (hr == MAPI_E_TOO_COMPLEX || hr == MAPI_E_INVALID_PARAMETER) {
- // ignore group nesting loop and non user/group types (eg. companies)
- hr = hrSuccess;
- } else if (hr != hrSuccess) {
- // eg. MAPI_E_NOT_FOUND
- ec_log_err("Error while expanding group. Group: %ls, error: 0x%08x", lpGroupName->Value.lpszW, hr);
- error = std::wstring(L"Error in group '") + lpGroupName->Value.lpszW + L"', unable to send e-mail";
- return hr;
- }
- } else if (setRecips.find(strEmail) == setRecips.end()) {
- recipients.appendMailbox(vmime::make_shared<vmime::mailbox>(convert_to<string>(strEmail)));
- setRecips.insert(strEmail);
- ec_log_debug("Sending to group-address %s instead of expanded list",
- convert_to<std::string>(strEmail).c_str());
- }
- }
- return hr;
- }
- /**
- * Takes a group entry of a recipient table and expands the recipients for the group recursively by adding them to the recipients list
- *
- * @param lpAdrBook Pointer to the addressbook for the user sending the message (important for multi-tenancy separation)
- * @param lpGroupName Pointer to PR_EMAIL_ADDRESS_W entry for the recipient in the recipient table
- * @param lpGroupEntryId Pointer to PR_ENTRYID entry for the recipient in the recipient table
- * @param recipients Reference to list of VMIME recipients to be appended to
- * @param setGroups Set of already-processed groups (used for loop detecting in group expansion)
- * @param setRecips Set of recipients already processed, used for duplicate-recip detection
- *
- * This function expands the specified group by opening the group and adding all user entries to the recipients list, and
- * recursively expanding groups in the group.
- *
- * lpGroupEntryID may be NULL, in which case lpGroupName is used to resolve the group via the addressbook. If
- * both parameters are set, lpGroupEntryID is used, and lpGroupName is ignored.
- */
- HRESULT ECVMIMESender::HrExpandGroup(LPADRBOOK lpAdrBook,
- const SPropValue *lpGroupName, const SPropValue *lpGroupEntryID,
- vmime::mailboxList &recipients, std::set<std::wstring> &setGroups,
- std::set<std::wstring> &setRecips, bool bAllowEveryone)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IDistList> lpGroup;
- ULONG ulType = 0;
- object_ptr<IMAPITable> lpTable;
- memory_ptr<SPropValue> lpEmailAddress;
- if (lpGroupEntryID == nullptr || lpAdrBook->OpenEntry(lpGroupEntryID->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpGroupEntryID->Value.bin.lpb), nullptr, 0, &ulType, &~lpGroup) != hrSuccess || ulType != MAPI_DISTLIST) {
- // 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)
- // Therefore resolve group name, and open that instead.
- if (lpGroupName == nullptr)
- return MAPI_E_NOT_FOUND;
- rowset_ptr lpRows;
- hr = MAPIAllocateBuffer(sizeof(SRowSet), &~lpRows);
- if (hr != hrSuccess)
- return hr;
- lpRows->cRows = 1;
- if ((hr = MAPIAllocateBuffer(sizeof(SPropValue), (void **)&lpRows->aRow[0].lpProps)) != hrSuccess)
- return hr;
- lpRows->aRow[0].cValues = 1;
-
- lpRows->aRow[0].lpProps[0].ulPropTag = PR_DISPLAY_NAME_W;
- lpRows->aRow[0].lpProps[0].Value.lpszW = lpGroupName->Value.lpszW;
- hr = lpAdrBook->ResolveName(0, MAPI_UNICODE | EMS_AB_ADDRESS_LOOKUP, NULL, reinterpret_cast<ADRLIST *>(lpRows.get()));
- if(hr != hrSuccess)
- return hr;
- lpGroupEntryID = PCpropFindProp(lpRows->aRow[0].lpProps, lpRows->aRow[0].cValues, PR_ENTRYID);
- if (lpGroupEntryID == nullptr)
- return MAPI_E_NOT_FOUND;
- // Open resolved entry
- hr = lpAdrBook->OpenEntry(lpGroupEntryID->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpGroupEntryID->Value.bin.lpb), nullptr, 0, &ulType, &~lpGroup);
- if(hr != hrSuccess)
- return hr;
-
- if(ulType != MAPI_DISTLIST) {
- ec_log_debug("Expected group, but opened type %d", ulType);
- return MAPI_E_INVALID_PARAMETER;
- }
- }
- hr = HrGetOneProp(lpGroup, PR_EMAIL_ADDRESS_W, &~lpEmailAddress);
- if(hr != hrSuccess)
- return hr;
- if (setGroups.find(lpEmailAddress->Value.lpszW) != setGroups.end())
- // Group loops in nesting
- return MAPI_E_TOO_COMPLEX;
-
- // Add group name to list of processed groups
- setGroups.insert(lpEmailAddress->Value.lpszW);
- hr = lpGroup->GetContentsTable(MAPI_UNICODE, &~lpTable);
- if(hr != hrSuccess)
- return hr;
- return HrAddRecipsFromTable(lpAdrBook, lpTable, recipients, setGroups,
- setRecips, bAllowEveryone, true);
- }
- HRESULT ECVMIMESender::HrMakeRecipientsList(LPADRBOOK lpAdrBook,
- LPMESSAGE lpMessage, vmime::shared_ptr<vmime::message> vmMessage,
- vmime::mailboxList &recipients, bool bAllowEveryone,
- bool bAlwaysExpandDistrList)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPITable> lpRTable;
- bool bResend = false;
- std::set<std::wstring> setGroups; // Set of groups to make sure we don't get into an expansion-loop
- std::set<std::wstring> setRecips; // Set of recipients to make sure we don't send two identical RCPT TO's
- memory_ptr<SPropValue> lpMessageFlags;
-
- hr = HrGetOneProp(lpMessage, PR_MESSAGE_FLAGS, &~lpMessageFlags);
- if (hr != hrSuccess)
- return hr;
- if(lpMessageFlags->Value.ul & MSGFLAG_RESEND)
- bResend = true;
- hr = lpMessage->GetRecipientTable(MAPI_UNICODE, &~lpRTable);
- if (hr != hrSuccess)
- return hr;
- // When resending, only send to MAPI_P1 recipients
- if(bResend) {
- SPropValue sRestrictProp;
- sRestrictProp.ulPropTag = PR_RECIPIENT_TYPE;
- sRestrictProp.Value.ul = MAPI_P1;
- hr = ECPropertyRestriction(RELOP_EQ, PR_RECIPIENT_TYPE,
- &sRestrictProp, ECRestriction::Cheap)
- .RestrictTable(lpRTable, TBL_BATCH);
- if (hr != hrSuccess)
- return hr;
- }
- return HrAddRecipsFromTable(lpAdrBook, lpRTable, recipients, setGroups,
- setRecips, bAllowEveryone, bAlwaysExpandDistrList);
- }
- // This function does not catch the vmime exception
- // it should be handled by the calling party.
- HRESULT ECVMIMESender::sendMail(LPADRBOOK lpAdrBook, LPMESSAGE lpMessage,
- vmime::shared_ptr<vmime::message> vmMessage, bool bAllowEveryone,
- bool bAlwaysExpandDistrList)
- {
- HRESULT hr = hrSuccess;
- vmime::mailbox expeditor;
- vmime::mailboxList recipients;
- vmime::shared_ptr<vmime::messaging::session> vmSession;
- vmime::shared_ptr<vmime::messaging::transport> vmTransport;
- vmime::shared_ptr<vmime::net::smtp::MAPISMTPTransport> mapiTransport;
- if (lpMessage == NULL || vmMessage == NULL)
- return MAPI_E_INVALID_PARAMETER;
- smtpresult = 0;
- error.clear();
- try {
- // Session initialization (global properties)
- vmSession = vmime::net::session::create();
- // set the server address and port, plus type of service by use of url
- // and get our special mapismtp mailer
- vmime::utility::url url("mapismtp", smtphost, smtpport);
- vmTransport = vmSession->getTransport(url);
- vmTransport->setTimeoutHandlerFactory(vmime::make_shared<mapiTimeoutHandlerFactory>());
- // cast to access interface extra's
- mapiTransport = vmime::dynamicCast<vmime::net::smtp::MAPISMTPTransport>(vmTransport);
- // get expeditor for 'mail from:' smtp command
- if (vmMessage->getHeader()->hasField(vmime::fields::FROM))
- expeditor = *vmime::dynamicCast<vmime::mailbox>(vmMessage->getHeader()->findField(vmime::fields::FROM)->getValue());
- else
- throw vmime::exceptions::no_expeditor();
- if (expeditor.isEmpty()) {
- // cancel this message as unsendable, would otherwise be thrown out of transport::send()
- error = L"No expeditor in e-mail";
- return MAPI_W_CANCEL_MESSAGE;
- }
- hr = HrMakeRecipientsList(lpAdrBook, lpMessage, vmMessage, recipients, bAllowEveryone, bAlwaysExpandDistrList);
- if (hr != hrSuccess)
- return hr;
- if (recipients.isEmpty()) {
- // cancel this message as unsendable, would otherwise be thrown out of transport::send()
- error = L"No recipients in e-mail";
- return MAPI_W_CANCEL_MESSAGE;
- }
- // Remove BCC headers from the message we're about to send
- if (vmMessage->getHeader()->hasField(vmime::fields::BCC)) {
- auto bcc = vmMessage->getHeader()->findField(vmime::fields::BCC);
- vmMessage->getHeader()->removeField(bcc);
- }
- // Delivery report request
- SPropValuePtr ptrDeliveryReport;
- if (mapiTransport != nullptr &&
- HrGetOneProp(lpMessage, PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED, &~ptrDeliveryReport) == hrSuccess &&
- ptrDeliveryReport->Value.b == TRUE)
- mapiTransport->requestDSN(true, "");
- // Generate the message, "stream" it and delegate the sending
- // to the generic send() function.
- std::ostringstream oss;
- vmime::utility::outputStreamAdapter ossAdapter(oss);
- vmMessage->generate(ossAdapter);
- const string& str(oss.str()); // copy?
- vmime::utility::inputStreamStringAdapter isAdapter(str); // direct oss.str() ?
-
- // send the email already!
- bool ok = false;
- try {
- vmTransport->connect();
- } catch (vmime::exception &e) {
- // special error, smtp server not respoding, so try later again
- ec_log_err("Connect to SMTP: %s. E-Mail will be tried again later.", e.what());
- return MAPI_W_NO_SERVICE;
- }
- try {
- vmTransport->send(expeditor, recipients, isAdapter, str.length(), NULL);
- vmTransport->disconnect();
- ok = true;
- }
- catch (vmime::exceptions::command_error& e) {
- if (mapiTransport != NULL) {
- mPermanentFailedRecipients = mapiTransport->getPermanentFailedRecipients();
- mTemporaryFailedRecipients = mapiTransport->getTemporaryFailedRecipients();
- }
- ec_log_err("SMTP: %s Response: %s", e.what(), e.response().c_str());
- smtpresult = atoi(e.response().substr(0, e.response().find_first_of(" ")).c_str());
- error = convert_to<wstring>(e.response());
- // message should be cancelled, unsendable, test by smtp result code.
- return MAPI_W_CANCEL_MESSAGE;
- }
- catch (vmime::exceptions::no_recipient& e) {
- if (mapiTransport != NULL) {
- mPermanentFailedRecipients = mapiTransport->getPermanentFailedRecipients();
- mTemporaryFailedRecipients = mapiTransport->getTemporaryFailedRecipients();
- }
- ec_log_err("SMTP: %s Name: %s", e.what(), e.name());
- //smtpresult = atoi(e.response().substr(0, e.response().find_first_of(" ")).c_str());
- //error = convert_to<wstring>(e.response());
- // message should be cancelled, unsendable, test by smtp result code.
- return MAPI_W_CANCEL_MESSAGE;
- }
- catch (vmime::exception &e) {
- }
- if (mapiTransport != NULL) {
- /*
- * Multiple invalid recipients can cause the opponent
- * mail server (e.g. Postfix) to disconnect. In that
- * case, fail those recipients.
- */
- mPermanentFailedRecipients = mapiTransport->getPermanentFailedRecipients();
- mTemporaryFailedRecipients = mapiTransport->getTemporaryFailedRecipients();
- if (mPermanentFailedRecipients.size() == static_cast<size_t>(recipients.getMailboxCount())) {
- ec_log_err("SMTP: e-mail will be not be tried again: all recipients failed.");
- return MAPI_W_CANCEL_MESSAGE;
- } else if (!mTemporaryFailedRecipients.empty()) {
- ec_log_err("SMTP: e-mail will be tried again: some recipients failed.");
- return MAPI_W_PARTIAL_COMPLETION;
- } else if (!mPermanentFailedRecipients.empty()) {
- ec_log_err("SMTP: some recipients failed.");
- return MAPI_W_PARTIAL_COMPLETION;
- } else if (mTemporaryFailedRecipients.empty() && mPermanentFailedRecipients.empty() && !ok) {
- // special error, smtp server not respoding, so try later again
- ec_log_err("SMTP: e-mail will be tried again.");
- return MAPI_W_NO_SERVICE;
- }
- }
- }
- catch (vmime::exception& e) {
- // connection_greeting_error, ...?
- ec_log_err("%s", e.what());
- error = convert_to<wstring>(e.what());
- return MAPI_E_NETWORK_ERROR;
- }
- catch (std::exception& e) {
- ec_log_err("%s", e.what());
- error = convert_to<wstring>(e.what());
- return MAPI_E_NETWORK_ERROR;
- }
- return hr;
- }
- } /* namespace */
|