123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660 |
- /*
- * 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/>.
- *
- */
- // based on src/net/smtp/SMTPTransport.cpp, but with additions
- // we cannot use a class derived from SMTPTransport, since that class has alot of privates
- //
- // VMime library (http://www.vmime.org)
- // Copyright (C) 2002-2009 Vincent Richard <vincent@vincent-richard.net>
- //
- // This program is free software; you can redistribute it and/or
- // modify it under the terms of the GNU General Public License as
- // published by the Free Software Foundation; either version 3 of
- // the License, or (at your option) any later version.
- //
- // 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
- // General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License along
- // with this program; if not, write to the Free Software Foundation, Inc.,
- // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- //
- // Linking this library statically or dynamically with other modules is making
- // a combined work based on this library. Thus, the terms and conditions of
- // the GNU General Public License cover the whole combination.
- //
- #include <kopano/platform.h>
- #include <memory>
- #include <utility>
- #include <kopano/tie.hpp>
- #include <kopano/stringutil.h>
- #include "MAPISMTPTransport.h"
- #include <vmime/net/smtp/SMTPResponse.hpp>
- #include <vmime/exception.hpp>
- #include <vmime/platform.hpp>
- #include <vmime/mailboxList.hpp>
- #include <vmime/utility/filteredStream.hpp>
- #include <vmime/utility/outputStreamSocketAdapter.hpp>
- #include <vmime/utility/streamUtils.hpp>
- #include <vmime/utility/stringUtils.hpp>
- #include <vmime/net/defaultConnectionInfos.hpp>
- #include <kopano/ECDebugPrint.h>
- #include <kopano/ECLogger.h>
- #include <kopano/charset/traits.h>
- #if VMIME_HAVE_SASL_SUPPORT
- # include <vmime/security/sasl/SASLContext.hpp>
- #endif // VMIME_HAVE_SASL_SUPPORT
- #if VMIME_HAVE_TLS_SUPPORT
- # include <vmime/net/tls/TLSSession.hpp>
- # include <vmime/net/tls/TLSSecuredConnectionInfos.hpp>
- #endif // VMIME_HAVE_TLS_SUPPORT
- // Helpers for service properties
- #define GET_PROPERTY(type, prop) \
- (getInfos().getPropertyValue <type>(getSession(), \
- dynamic_cast <const SMTPServiceInfos&>(getInfos()).getProperties().prop))
- #define HAS_PROPERTY(prop) \
- (getInfos().hasProperty(getSession(), \
- dynamic_cast <const SMTPServiceInfos&>(getInfos()).getProperties().prop))
- // register new service, really hacked from (src/net/builtinServices.inl)
- #include "serviceRegistration.inl"
- REGISTER_SERVICE(smtp::MAPISMTPTransport, mapismtp, TYPE_TRANSPORT);
- using namespace KCHL;
- namespace vmime {
- namespace net {
- namespace smtp {
- MAPISMTPTransport::MAPISMTPTransport(vmime::shared_ptr<session> sess,
- vmime::shared_ptr<security::authenticator> auth, const bool secured) :
- transport(sess, getInfosInstance(), auth), m_isSMTPS(secured)
- {
- }
- MAPISMTPTransport::~MAPISMTPTransport()
- {
- try
- {
- if (isConnected())
- disconnect();
- else if (m_socket)
- internalDisconnect();
- }
- catch (vmime::exception&)
- {
- // Ignore
- }
- }
- void MAPISMTPTransport::connect()
- {
- if (isConnected())
- throw exceptions::already_connected();
- const string address = GET_PROPERTY(string, PROPERTY_SERVER_ADDRESS);
- const port_t port = GET_PROPERTY(port_t, PROPERTY_SERVER_PORT);
- // Create the time-out handler
- if (getTimeoutHandlerFactory())
- m_timeoutHandler = getTimeoutHandlerFactory()->create();
- // Create and connect the socket
- // @note we don't want a timeout during the connect() call
- // because if we set this, the side-effect is when IPv6 is tried first, it will timeout
- // the handler will break the loop by returning false from the handleTimeOut() function.
- m_socket = getSocketFactory()->create();
- #if VMIME_HAVE_TLS_SUPPORT
- if (m_isSMTPS) // dedicated port/SMTPS
- {
- auto tlsSession = tls::TLSSession::create(getCertificateVerifier(), getSession()->getTLSProperties());
- auto tlsSocket = tlsSession->getSocket(m_socket);
- m_socket = tlsSocket;
- m_secured = true;
- m_cntInfos = vmime::make_shared<tls::TLSSecuredConnectionInfos>(address, port, tlsSession, tlsSocket);
- }
- else
- #endif // VMIME_HAVE_TLS_SUPPORT
- {
- m_cntInfos = vmime::make_shared<defaultConnectionInfos>(address, port);
- }
- ec_log_debug("SMTP connecting to %s:%d", address.c_str(), port);
- m_socket->connect(address, port);
- ec_log_debug("SMTP server connected.");
- // Connection
- //
- // eg: C: <connection to server>
- // --- S: 220 smtp.domain.com Service ready
- vmime::shared_ptr<SMTPResponse> resp;
- if ((resp = readResponse())->getCode() != 220)
- {
- internalDisconnect();
- throw exceptions::connection_greeting_error(resp->getText());
- }
- // Identification
- helo();
- #if VMIME_HAVE_TLS_SUPPORT
- // Setup secured connection, if requested
- const bool tls = HAS_PROPERTY(PROPERTY_CONNECTION_TLS)
- && GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS);
- const bool tlsRequired = HAS_PROPERTY(PROPERTY_CONNECTION_TLS_REQUIRED)
- && GET_PROPERTY(bool, PROPERTY_CONNECTION_TLS_REQUIRED);
- if (!m_isSMTPS && tls) // only if not SMTPS
- {
- try
- {
- startTLS();
- }
- // Non-fatal error
- catch (exceptions::command_error&)
- {
- if (tlsRequired)
- {
- throw;
- }
- else
- {
- // TLS is not required, so don't bother
- }
- }
- // Fatal error
- catch (...)
- {
- throw;
- }
- // Must reissue a EHLO command [RFC-2487, 5.2]
- helo();
- }
- #endif // VMIME_HAVE_TLS_SUPPORT
- // Authentication
- if (GET_PROPERTY(bool, PROPERTY_OPTIONS_NEEDAUTH))
- authenticate();
- else
- m_authentified = true;
- }
- void MAPISMTPTransport::helo()
- {
- // First, try Extended SMTP (ESMTP)
- //
- // eg: C: EHLO thismachine.ourdomain.com
- // S: 250-smtp.theserver.com
- // S: 250-AUTH CRAM-MD5 DIGEST-MD5
- // S: 250-PIPELINING
- // S: 250 SIZE 2555555555
- sendRequest("EHLO " + platform::getHandler()->getHostName());
- vmime::shared_ptr<SMTPResponse> resp;
- if ((resp = readResponse())->getCode() != 250)
- {
- // Next, try "Basic" SMTP
- //
- // eg: C: HELO thismachine.ourdomain.com
- // S: 250 OK
- sendRequest("HELO " + platform::getHandler()->getHostName());
- if ((resp = readResponse())->getCode() != 250)
- {
- internalDisconnect();
- throw exceptions::connection_greeting_error(resp->getLastLine().getText());
- }
- m_extendedSMTP = false;
- m_extensions.clear();
- }
- else
- {
- m_extendedSMTP = true;
- m_extensions.clear();
- // Get supported extensions from SMTP response
- // One extension per line, format is: EXT PARAM1 PARAM2...
- for (int i = 1, n = resp->getLineCount() ; i < n ; ++i)
- {
- const string line = resp->getLineAt(i).getText();
- std::istringstream iss(line);
- string ext;
- iss >> ext;
- std::vector <string> params;
- string param;
- // Special case: some servers send "AUTH=MECH [MECH MECH...]"
- if (ext.length() >= 5 && utility::stringUtils::toUpper(ext.substr(0, 5)) == "AUTH=")
- {
- params.push_back(utility::stringUtils::toUpper(ext.substr(5)));
- ext = "AUTH";
- }
- while (iss >> param)
- params.push_back(utility::stringUtils::toUpper(param));
- m_extensions[ext] = params;
- }
- }
- }
- void MAPISMTPTransport::authenticate()
- {
- if (!m_extendedSMTP)
- {
- internalDisconnect();
- throw exceptions::command_error("AUTH", "ESMTP not supported.");
- }
- getAuthenticator()->setService(vmime::dynamicCast<service>(shared_from_this()));
- #if VMIME_HAVE_SASL_SUPPORT
- // First, try SASL authentication
- if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL))
- {
- try
- {
- authenticateSASL();
- m_authentified = true;
- return;
- }
- catch (exceptions::authentication_error& e)
- {
- if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL_FALLBACK))
- {
- // Can't fallback on normal authentication
- internalDisconnect();
- throw e;
- }
- else
- {
- // Ignore, will try normal authentication
- }
- }
- catch (exception& e)
- {
- internalDisconnect();
- throw e;
- }
- }
- #endif // VMIME_HAVE_SASL_SUPPORT
- // No other authentication method is possible
- throw exceptions::authentication_error("All authentication methods failed");
- }
- #if VMIME_HAVE_SASL_SUPPORT
- void MAPISMTPTransport::authenticateSASL()
- {
- if (!vmime::dynamicCast<security::sasl::SASLAuthenticator>(getAuthenticator()))
- throw exceptions::authentication_error("No SASL authenticator available.");
- // Obtain SASL mechanisms supported by server from ESMTP extensions
- const std::vector <string> saslMechs =
- (m_extensions.find("AUTH") != m_extensions.end())
- ? m_extensions["AUTH"] : std::vector <string>();
- if (saslMechs.empty())
- throw exceptions::authentication_error("No SASL mechanism available.");
- std::vector<vmime::shared_ptr<security::sasl::SASLMechanism> > mechList;
- auto saslContext = security::sasl::SASLContext::create();
- for (unsigned int i = 0 ; i < saslMechs.size() ; ++i)
- {
- try
- {
- mechList.push_back
- (saslContext->createMechanism(saslMechs[i]));
- }
- catch (exceptions::no_such_mechanism&)
- {
- // Ignore mechanism
- }
- }
- if (mechList.empty())
- throw exceptions::authentication_error("No SASL mechanism available.");
- // Try to suggest a mechanism among all those supported
- auto suggestedMech = saslContext->suggestMechanism(mechList);
- if (!suggestedMech)
- throw exceptions::authentication_error("Unable to suggest SASL mechanism.");
- // Allow application to choose which mechanisms to use
- mechList = vmime::dynamicCast<security::sasl::SASLAuthenticator>(getAuthenticator())->
- getAcceptableMechanisms(mechList, suggestedMech);
- if (mechList.empty())
- throw exceptions::authentication_error("No SASL mechanism available.");
- // Try each mechanism in the list in turn
- for (unsigned int i = 0 ; i < mechList.size() ; ++i)
- {
- auto mech = mechList[i];
- auto saslSession = saslContext->createSession("smtp", getAuthenticator(), mech);
- saslSession->init();
- sendRequest("AUTH " + mech->getName());
- for (bool cont = true ; cont ; )
- {
- auto response = readResponse();
- switch (response->getCode())
- {
- case 235:
- {
- m_socket = saslSession->getSecuredSocket(m_socket);
- return;
- }
- case 334:
- {
- std::unique_ptr<byte_t[]> challenge, resp;
- size_t challengeLen = 0, respLen = 0;
- try
- {
- // Extract challenge
- saslContext->decodeB64(response->getText(), &unique_tie(challenge), &challengeLen);
- // Prepare response
- saslSession->evaluateChallenge(challenge.get(), challengeLen, &unique_tie(resp), &respLen);
- // Send response
- sendRequest(saslContext->encodeB64(resp.get(), respLen));
- }
- catch (exceptions::sasl_exception& e)
- {
- // Cancel SASL exchange
- sendRequest("*");
- }
- break;
- }
- default:
- cont = false;
- break;
- }
- }
- }
- throw exceptions::authentication_error
- ("Could not authenticate using SASL: all mechanisms failed.");
- }
- #endif // VMIME_HAVE_SASL_SUPPORT
- #if VMIME_HAVE_TLS_SUPPORT
- void MAPISMTPTransport::startTLS()
- {
- try
- {
- sendRequest("STARTTLS");
- auto resp = readResponse();
- if (resp->getCode() != 220)
- throw exceptions::command_error("STARTTLS", resp->getText());
- auto tlsSession = tls::TLSSession::create(getCertificateVerifier(), getSession()->getTLSProperties());
- auto tlsSocket = tlsSession->getSocket(m_socket);
- tlsSocket->handshake();
- m_socket = tlsSocket;
- m_secured = true;
- m_cntInfos = vmime::make_shared<tls::TLSSecuredConnectionInfos>
- (m_cntInfos->getHost(), m_cntInfos->getPort(), tlsSession, tlsSocket);
- }
- catch (exceptions::command_error&)
- {
- // Non-fatal error
- throw;
- }
- catch (exception&)
- {
- // Fatal error
- internalDisconnect();
- throw;
- }
- }
- #endif // VMIME_HAVE_TLS_SUPPORT
- bool MAPISMTPTransport::isConnected() const
- {
- return (m_socket && m_socket->isConnected() && m_authentified);
- }
- void MAPISMTPTransport::disconnect()
- {
- if (!isConnected())
- throw exceptions::not_connected();
- internalDisconnect();
- }
- void MAPISMTPTransport::internalDisconnect()
- {
- try
- {
- sendRequest("QUIT");
- }
- catch (exception&)
- {
- // Not important
- }
- m_socket->disconnect();
- m_socket = NULL;
- m_timeoutHandler = NULL;
- m_authentified = false;
- m_extendedSMTP = false;
- m_secured = false;
- m_cntInfos = NULL;
- }
- void MAPISMTPTransport::noop()
- {
- if (!isConnected())
- throw exceptions::not_connected();
- sendRequest("NOOP");
- auto resp = readResponse();
- if (resp->getCode() != 250)
- throw exceptions::command_error("NOOP", resp->getText());
- }
- //
- // Only this function is altered, to return per recipient failure.
- //
- void MAPISMTPTransport::send(const mailbox &expeditor,
- const mailboxList &recipients, utility::inputStream &is, size_t size,
- utility::progressListener *progress, const mailbox &sender)
- {
- if (!isConnected())
- throw exceptions::not_connected();
- // If no recipient/expeditor was found, throw an exception
- if (recipients.isEmpty())
- throw exceptions::no_recipient();
- else if (expeditor.isEmpty())
- throw exceptions::no_expeditor();
- // Emit the "MAIL" command
- vmime::shared_ptr<SMTPResponse> resp;
- string strSend;
- bool bDSN = m_bDSNRequest;
-
- if(bDSN && m_extensions.find("DSN") == m_extensions.end()) {
- ec_log_notice("SMTP server does not support Delivery Status Notifications (DSN)");
- bDSN = false; // Disable DSN because the server does not support this.
- }
- strSend = "MAIL FROM: <" + expeditor.getEmail().toString() + ">";
- if (bDSN) {
- strSend += " RET=HDRS";
- if (!m_strDSNTrackid.empty())
- strSend += " ENVID=" + m_strDSNTrackid;
- }
- sendRequest(strSend);
- resp = readResponse();
- if (resp->getCode() / 10 != 25) {
- internalDisconnect();
- throw exceptions::command_error("MAIL", resp->getText());
- }
- // Emit a "RCPT TO" command for each recipient
- mTemporaryFailedRecipients.clear();
- mPermanentFailedRecipients.clear();
- for (size_t i = 0 ; i < recipients.getMailboxCount(); ++i) {
- const mailbox& mbox = *recipients.getMailboxAt(i);
- unsigned int code;
- strSend = "RCPT TO: <" + mbox.getEmail().toString() + ">";
- if (bDSN)
- strSend += " NOTIFY=SUCCESS,DELAY";
- sendRequest(strSend);
- resp = readResponse();
- code = resp->getCode();
- sFailedRecip entry;
- auto recip_name = mbox.getName().getConvertedText(charset(CHARSET_WCHAR));
- entry.strRecipName.assign(reinterpret_cast<const wchar_t *>(recip_name.c_str()), recip_name.length() / sizeof(wchar_t));
- entry.strRecipEmail = mbox.getEmail().toString();
- entry.ulSMTPcode = code;
- entry.strSMTPResponse = resp->getText();
- if (code / 10 == 25) {
- continue;
- } else if (code == 421) {
- /* 421 4.7.0 localhorse.lh Error: too many errors */
- ec_log_err("RCPT line gave SMTP error: %d %s. (and now?)",
- resp->getCode(), resp->getText().c_str());
- break;
- } else if (code / 100 == 5) {
- /*
- * Example Postfix codes:
- * 501 5.1.3 Bad recipient address syntax (RCPT TO: <with spaces>)
- * 550 5.1.1 <fox>: Recipient address rejected: User unknown in virtual mailbox table
- * 550 5.7.1 REJECT action without code by means of e.g. /etc/postfix/header_checks
- */
- mPermanentFailedRecipients.push_back(std::move(entry));
- ec_log_err("RCPT line gave SMTP error %d %s. (no retry)",
- resp->getCode(), resp->getText().c_str());
- continue;
- } else if (code / 100 != 4) {
- mPermanentFailedRecipients.push_back(std::move(entry));
- ec_log_err("RCPT line gave unexpected SMTP reply %d %s. (no retry)",
- resp->getCode(), resp->getText().c_str());
- continue;
- }
- /* Other 4xx codes (disk full, ... ?) */
- mTemporaryFailedRecipients.push_back(std::move(entry));
- ec_log_err("RCPT line gave SMTP error: %d %s. (will be retried)",
- resp->getCode(), resp->getText().c_str());
- }
- // Send the message data
- sendRequest("DATA");
- // we also stop here if all recipients failed before
- if ((resp = readResponse())->getCode() != 354)
- {
- internalDisconnect();
- throw exceptions::command_error("DATA", format("%d %s", resp->getCode(), resp->getText().c_str()));
- }
- // Stream copy with "\n." to "\n.." transformation
- utility::outputStreamSocketAdapter sos(*m_socket);
- utility::dotFilteredOutputStream fos(sos);
- utility::bufferedStreamCopy(is, fos, size, progress);
- fos.flush();
- // Send end-of-data delimiter
- m_socket->sendRaw(reinterpret_cast<const vmime::byte_t *>("\r\n.\r\n"), 5);
- if ((resp = readResponse())->getCode() != 250)
- {
- internalDisconnect();
- throw exceptions::command_error("DATA", format("%d %s", resp->getCode(), resp->getText().c_str()));
- return;
- }
- // postfix: 2.0.0 Ok: queued as B36E73608E
- // qmail: ok 1295860788 qp 29154
- // exim: OK id=1PhIZ9-0002Ko-Q8
- ec_log_debug("SMTP: %s", resp->getText().c_str());
- }
- void MAPISMTPTransport::requestDSN(BOOL bRequest, const std::string &strTrackid)
- {
- m_bDSNRequest = bRequest;
- m_strDSNTrackid = strTrackid;
- }
- void MAPISMTPTransport::sendRequest(const string& buffer, const bool end)
- {
- ec_log_debug("< %s", buffer.c_str());
- if (end)
- m_socket->send(buffer + "\r\n");
- else
- m_socket->send(buffer);
- }
- vmime::shared_ptr<SMTPResponse> MAPISMTPTransport::readResponse(void)
- {
- vmime::shared_ptr<tracer> t;
- auto resp = SMTPResponse::readResponse(t, m_socket, m_timeoutHandler, m_response_state);
- m_response_state = resp->getCurrentState();
- ec_log_debug("> %d %s", resp->getCode(), resp->getText().c_str());
- return resp;
- }
- // Service infos
- SMTPServiceInfos MAPISMTPTransport::sm_infos(false);
- } // smtp
- } // net
- } // vmime
|