aboutsummaryrefslogtreecommitdiff
path: root/vmime-master/src/vmime/net/smtp/SMTPConnection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'vmime-master/src/vmime/net/smtp/SMTPConnection.cpp')
-rw-r--r--vmime-master/src/vmime/net/smtp/SMTPConnection.cpp714
1 files changed, 714 insertions, 0 deletions
diff --git a/vmime-master/src/vmime/net/smtp/SMTPConnection.cpp b/vmime-master/src/vmime/net/smtp/SMTPConnection.cpp
new file mode 100644
index 0000000..07d0376
--- /dev/null
+++ b/vmime-master/src/vmime/net/smtp/SMTPConnection.cpp
@@ -0,0 +1,714 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002 Vincent Richard <vincent@vmime.org>
+//
+// 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 "vmime/config.hpp"
+
+
+#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP
+
+
+#include "vmime/net/smtp/SMTPConnection.hpp"
+#include "vmime/net/smtp/SMTPTransport.hpp"
+#include "vmime/net/smtp/SMTPExceptions.hpp"
+
+#include "vmime/exception.hpp"
+#include "vmime/platform.hpp"
+
+#include "vmime/security/digest/messageDigestFactory.hpp"
+
+#include "vmime/net/defaultConnectionInfos.hpp"
+
+#if VMIME_HAVE_SASL_SUPPORT
+ #include "vmime/security/sasl/SASLContext.hpp"
+#else
+ #include "vmime/utility/encoder/b64Encoder.hpp"
+ #include "vmime/utility/inputStreamStringAdapter.hpp"
+ #include "vmime/utility/outputStreamStringAdapter.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) \
+ (m_transport.lock()->getInfos().getPropertyValue <type>(getSession(), \
+ dynamic_cast <const SMTPServiceInfos&>(m_transport.lock()->getInfos()).getProperties().prop))
+#define HAS_PROPERTY(prop) \
+ (m_transport.lock()->getInfos().hasProperty(getSession(), \
+ dynamic_cast <const SMTPServiceInfos&>(m_transport.lock()->getInfos()).getProperties().prop))
+
+
+namespace vmime {
+namespace net {
+namespace smtp {
+
+
+
+SMTPConnection::SMTPConnection(
+ const shared_ptr <SMTPTransport>& transport,
+ const shared_ptr <security::authenticator>& auth
+)
+ : m_transport(transport),
+ m_auth(auth),
+ m_socket(null),
+ m_timeoutHandler(null),
+ m_authenticated(false),
+ m_secured(false),
+ m_extendedSMTP(false) {
+
+ static int connectionId = 0;
+
+ if (transport->getTracerFactory()) {
+ m_tracer = transport->getTracerFactory()->create(transport, ++connectionId);
+ }
+}
+
+
+SMTPConnection::~SMTPConnection() {
+
+ try {
+
+ if (isConnected()) {
+ disconnect();
+ } else if (m_socket) {
+ internalDisconnect();
+ }
+
+ } catch (...) {
+
+ // Don't throw in destructor
+ }
+}
+
+
+void SMTPConnection::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);
+
+ shared_ptr <SMTPTransport> transport = m_transport.lock();
+
+ // Create the time-out handler
+ if (transport->getTimeoutHandlerFactory()) {
+ m_timeoutHandler = transport->getTimeoutHandlerFactory()->create();
+ }
+
+ // Create and connect the socket
+ m_socket = transport->getSocketFactory()->create(m_timeoutHandler);
+ m_socket->setTracer(m_tracer);
+
+#if VMIME_HAVE_TLS_SUPPORT
+ if (transport->isSMTPS()) { // dedicated port/SMTPS
+
+ shared_ptr <tls::TLSSession> tlsSession = tls::TLSSession::create(
+ transport->getCertificateVerifier(),
+ transport->getSession()->getTLSProperties()
+ );
+
+ shared_ptr <tls::TLSSocket> tlsSocket = tlsSession->getSocket(m_socket);
+
+ m_socket = tlsSocket;
+
+ m_secured = true;
+ m_cntInfos = make_shared <tls::TLSSecuredConnectionInfos>(address, port, tlsSession, tlsSocket);
+
+ } else
+#endif // VMIME_HAVE_TLS_SUPPORT
+ {
+ m_cntInfos = make_shared <defaultConnectionInfos>(address, port);
+ }
+
+ m_socket->connect(address, port);
+
+ // Connection
+ //
+ // eg: C: <connection to server>
+ // --- S: 220 smtp.domain.com Service ready
+
+ 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 (!transport->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_authenticated = true;
+ }
+}
+
+
+void SMTPConnection::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(SMTPCommand::EHLO(platform::getHandler()->getHostName()));
+
+ shared_ptr <SMTPResponse> resp;
+
+ if ((resp = readResponse())->getCode() != 250) {
+
+ // Next, try "Basic" SMTP
+ //
+ // eg: C: HELO thismachine.ourdomain.com
+ // S: 250 OK
+
+ sendRequest(SMTPCommand::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 (size_t i = 1, n = resp->getLineCount() ; i < n ; ++i) {
+
+ const string line = resp->getLineAt(i).getText();
+
+ std::istringstream iss(line);
+ iss.imbue(std::locale::classic());
+
+ 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;
+ }
+ }
+}
+
+
+bool SMTPConnection::hasExtension(
+ const std::string& extName,
+ std::vector <string>* params
+) const {
+
+ std::map <string, std::vector <string> >::const_iterator
+ it = m_extensions.find(extName);
+
+ if (it != m_extensions.end()) {
+
+ if (params) {
+ *params = (*it).second;
+ }
+
+ return true;
+
+ } else {
+
+ return false;
+ }
+}
+
+
+void SMTPConnection::authenticate() {
+
+ if (!m_extendedSMTP) {
+ internalDisconnect();
+ throw exceptions::command_error("AUTH", "ESMTP not supported.");
+ }
+
+ getAuthenticator()->setService(m_transport.lock());
+
+#if VMIME_HAVE_SASL_SUPPORT
+ // Try SASL authentication
+ if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL)) {
+
+ try {
+
+ authenticateSASL();
+
+ m_authenticated = true;
+ return;
+
+ } catch (exception&) {
+
+ internalDisconnect();
+ throw;
+ }
+ }
+#else // no SASL
+
+ // allow AUTH PLAIN over TLS - it is a popular and simple mechanism
+ if (m_secured) {
+
+ std::vector <string> authMechs;
+ hasExtension("AUTH", &authMechs);
+
+ if (authMechs.empty()) {
+ throw exceptions::authentication_error("No AUTH mechanism available.");
+ }
+
+ const string plain("PLAIN");
+
+ if (std::find(authMechs.begin(), authMechs.end(), plain) != authMechs.end()) {
+
+ const string username = getAuthenticator()->getUsername();
+ const string password = getAuthenticator()->getPassword();
+ const string authToken = username + '\0' + username + '\0' + password;
+
+ utility::inputStreamStringAdapter in(authToken);
+ string authTokenBase64;
+ utility::outputStreamStringAdapter out(authTokenBase64);
+
+ vmime::utility::encoder::b64Encoder encoder;
+ encoder.encode(in, out);
+
+ sendRequest(SMTPCommand::AUTH(plain, authTokenBase64));
+
+ shared_ptr <SMTPResponse> response = readResponse();
+
+ const int code = response ? response->getCode() : -1;
+
+ if (code == 235) {
+ m_authenticated = true;
+ return;
+ }
+ }
+ }
+
+#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 SMTPConnection::authenticateSASL() {
+
+ if (!dynamicCast <security::sasl::SASLAuthenticator>(getAuthenticator())) {
+ throw exceptions::authentication_error("No SASL authenticator available.");
+ }
+
+ // Obtain SASL mechanisms supported by server from ESMTP extensions
+ std::vector <string> saslMechs;
+ hasExtension("AUTH", &saslMechs);
+
+ if (saslMechs.empty()) {
+ throw exceptions::authentication_error("No SASL mechanism available.");
+ }
+
+ std::vector <shared_ptr <security::sasl::SASLMechanism> > mechList;
+
+ shared_ptr <security::sasl::SASLContext> 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
+ shared_ptr <security::sasl::SASLMechanism> suggestedMech =
+ saslContext->suggestMechanism(mechList);
+
+ if (!suggestedMech) {
+ throw exceptions::authentication_error("Unable to suggest SASL mechanism.");
+ }
+
+ // Allow application to choose which mechanisms to use
+ mechList = 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) {
+
+ shared_ptr <security::sasl::SASLMechanism> mech = mechList[i];
+
+ shared_ptr <security::sasl::SASLSession> saslSession =
+ saslContext->createSession("smtp", getAuthenticator(), mech);
+
+ saslSession->init();
+
+ if (saslSession->getMechanism()->hasInitialResponse()) {
+
+ byte_t* initialResp = 0;
+ size_t initialRespLen = 0;
+
+ saslSession->evaluateChallenge(NULL, 0, &initialResp, &initialRespLen);
+
+ string encodedInitialResp(saslContext->encodeB64(initialResp, initialRespLen));
+ delete [] initialResp;
+
+ if (encodedInitialResp.empty()) {
+ sendRequest(SMTPCommand::AUTH(mech->getName(), "="));
+ } else {
+ sendRequest(SMTPCommand::AUTH(mech->getName(), encodedInitialResp));
+ }
+
+ } else {
+
+ sendRequest(SMTPCommand::AUTH(mech->getName()));
+ }
+
+ for (bool cont = true ; cont ; ) {
+
+ shared_ptr <SMTPResponse> response = readResponse();
+
+ switch (response->getCode()) {
+
+ case 235: {
+
+ m_socket = saslSession->getSecuredSocket(m_socket);
+ return;
+ }
+ case 334: {
+
+ byte_t* challenge = 0;
+ size_t challengeLen = 0;
+
+ byte_t* resp = 0;
+ size_t respLen = 0;
+
+ try {
+
+ // Extract challenge
+ saslContext->decodeB64(response->getText(), &challenge, &challengeLen);
+
+ // Prepare response
+ saslSession->evaluateChallenge(challenge, challengeLen, &resp, &respLen);
+
+ // Send response
+ const string respB64 = saslContext->encodeB64(resp, respLen) + "\r\n";
+ m_socket->sendRaw(utility::stringUtils::bytesFromString(respB64), respB64.length());
+
+ if (m_tracer) {
+ m_tracer->traceSendBytes(respB64.length() - 2, "SASL exchange");
+ }
+
+ } catch (exceptions::sasl_exception& e) {
+
+ if (challenge) {
+ delete [] challenge;
+ challenge = NULL;
+ }
+
+ if (resp) {
+ delete [] resp;
+ resp = NULL;
+ }
+
+ // Cancel SASL exchange
+ m_socket->send("*\r\n");
+
+ if (m_tracer) {
+ m_tracer->traceSend("*");
+ }
+
+ } catch (...) {
+
+ if (challenge) {
+ delete [] challenge;
+ }
+
+ if (resp) {
+ delete [] resp;
+ }
+
+ throw;
+ }
+
+ if (challenge) {
+ delete [] challenge;
+ }
+
+ if (resp) {
+ delete [] resp;
+ }
+
+ 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 SMTPConnection::startTLS() {
+
+ try {
+
+ sendRequest(SMTPCommand::STARTTLS());
+
+ shared_ptr <SMTPResponse> resp = readResponse();
+
+ if (resp->getCode() != 220) {
+
+ throw SMTPCommandError(
+ "STARTTLS", resp->getText(), resp->getCode(), resp->getEnhancedCode()
+ );
+ }
+
+ shared_ptr <tls::TLSSession> tlsSession = tls::TLSSession::create(
+ getTransport()->getCertificateVerifier(),
+ getTransport()->getSession()->getTLSProperties()
+ );
+
+ shared_ptr <tls::TLSSocket> tlsSocket = tlsSession->getSocket(m_socket);
+
+ tlsSocket->handshake();
+
+ m_socket = tlsSocket;
+
+ m_secured = true;
+ m_cntInfos = 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
+
+
+void SMTPConnection::disconnect() {
+
+ if (!isConnected()) {
+ throw exceptions::not_connected();
+ }
+
+ internalDisconnect();
+}
+
+
+void SMTPConnection::internalDisconnect() {
+
+ if (isConnected()) {
+
+ try {
+
+ sendRequest(SMTPCommand::QUIT());
+
+ // Do not wait for server response. This is contrary to the RFC, but
+ // some servers never send a response to a QUIT command.
+
+ } catch (exception&) {
+
+ // Not important
+ }
+ }
+
+ m_socket->disconnect();
+ m_socket = null;
+
+ m_timeoutHandler = null;
+
+ m_authenticated = false;
+ m_extendedSMTP = false;
+
+ m_secured = false;
+ m_cntInfos = null;
+}
+
+
+void SMTPConnection::sendRequest(const shared_ptr <SMTPCommand>& cmd) {
+
+ cmd->writeToSocket(m_socket, m_tracer);
+}
+
+
+shared_ptr <SMTPResponse> SMTPConnection::readResponse() {
+
+ shared_ptr <SMTPResponse> resp = SMTPResponse::readResponse(
+ m_tracer, m_socket, m_timeoutHandler, m_responseState
+ );
+
+ m_responseState = resp->getCurrentState();
+
+ return resp;
+}
+
+
+bool SMTPConnection::isConnected() const {
+
+ return m_socket && m_socket->isConnected() && m_authenticated;
+}
+
+
+bool SMTPConnection::isSecuredConnection() const {
+
+ return m_secured;
+}
+
+
+shared_ptr <connectionInfos> SMTPConnection::getConnectionInfos() const {
+
+ return m_cntInfos;
+}
+
+
+shared_ptr <SMTPTransport> SMTPConnection::getTransport() {
+
+ return m_transport.lock();
+}
+
+
+shared_ptr <session> SMTPConnection::getSession() {
+
+ return m_transport.lock()->getSession();
+}
+
+
+shared_ptr <socket> SMTPConnection::getSocket() {
+
+ return m_socket;
+}
+
+
+shared_ptr <tracer> SMTPConnection::getTracer() {
+
+ return m_tracer;
+}
+
+
+shared_ptr <timeoutHandler> SMTPConnection::getTimeoutHandler() {
+
+ return m_timeoutHandler;
+}
+
+
+shared_ptr <security::authenticator> SMTPConnection::getAuthenticator() {
+
+ return m_auth;
+}
+
+
+} // smtp
+} // net
+} // vmime
+
+
+#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SMTP