diff options
author | Wojtek Kosior <wk@koszkonutek-tmp.pl.eu.org> | 2021-04-30 00:33:56 +0200 |
---|---|---|
committer | Wojtek Kosior <wk@koszkonutek-tmp.pl.eu.org> | 2021-04-30 00:33:56 +0200 |
commit | aa4d426b4d3527d7e166df1a05058c9a4a0f6683 (patch) | |
tree | 4ff17ce8b89a2321b9d0ed4bcfc37c447bcb6820 /vmime-master/src/vmime/net/pop3/POP3Connection.cpp | |
download | smtps-and-pop3s-console-program-master.tar.gz smtps-and-pop3s-console-program-master.zip |
Diffstat (limited to 'vmime-master/src/vmime/net/pop3/POP3Connection.cpp')
-rw-r--r-- | vmime-master/src/vmime/net/pop3/POP3Connection.cpp | 737 |
1 files changed, 737 insertions, 0 deletions
diff --git a/vmime-master/src/vmime/net/pop3/POP3Connection.cpp b/vmime-master/src/vmime/net/pop3/POP3Connection.cpp new file mode 100644 index 0000000..749f7ef --- /dev/null +++ b/vmime-master/src/vmime/net/pop3/POP3Connection.cpp @@ -0,0 +1,737 @@ +// +// 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_POP3 + + +#include "vmime/net/pop3/POP3Connection.hpp" +#include "vmime/net/pop3/POP3Store.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" +#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_store.lock()->getInfos().getPropertyValue <type>(getSession(), \ + dynamic_cast <const POP3ServiceInfos&>(m_store.lock()->getInfos()).getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (m_store.lock()->getInfos().hasProperty(getSession(), \ + dynamic_cast <const POP3ServiceInfos&>(m_store.lock()->getInfos()).getProperties().prop)) + + +namespace vmime { +namespace net { +namespace pop3 { + + + +POP3Connection::POP3Connection( + const shared_ptr <POP3Store>& store, + const shared_ptr <security::authenticator>& auth +) + : m_store(store), + m_auth(auth), + m_socket(null), + m_timeoutHandler(null), + m_authenticated(false), + m_secured(false), + m_capabilitiesFetched(false) { + + static int connectionId = 0; + + if (store->getTracerFactory()) { + m_tracer = store->getTracerFactory()->create(store, ++connectionId); + } +} + + +POP3Connection::~POP3Connection() { + + try { + + if (isConnected()) { + disconnect(); + } else if (m_socket) { + internalDisconnect(); + } + + } catch (...) { + + // Don't throw in destructor + } +} + + +void POP3Connection::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 <POP3Store> store = m_store.lock(); + + // Create the time-out handler + if (store->getTimeoutHandlerFactory()) { + m_timeoutHandler = store->getTimeoutHandlerFactory()->create(); + } + + // Create and connect the socket + m_socket = store->getSocketFactory()->create(m_timeoutHandler); + m_socket->setTracer(m_tracer); + +#if VMIME_HAVE_TLS_SUPPORT + if (store->isPOP3S()) { // dedicated port/POP3S + + shared_ptr <tls::TLSSession> tlsSession = tls::TLSSession::create + (store->getCertificateVerifier(), + store->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: +OK MailSite POP3 Server 5.3.4.0 Ready <36938848.1056800841.634@somewhere.com> + + shared_ptr <POP3Response> response = POP3Response::readResponse( + dynamicCast <POP3Connection>(shared_from_this()) + ); + + if (!response->isSuccess()) { + + internalDisconnect(); + throw exceptions::connection_greeting_error(response->getFirstLine()); + } + +#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 (!store->isPOP3S() && tls) { // only if not POP3S + + 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; + } + } +#endif // VMIME_HAVE_TLS_SUPPORT + + // Start authentication process + authenticate(messageId(response->getText())); +} + + +void POP3Connection::disconnect() { + + if (!isConnected()) { + throw exceptions::not_connected(); + } + + internalDisconnect(); +} + + +void POP3Connection::internalDisconnect() { + + if (m_socket) { + + if (m_socket->isConnected()) { + + try { + + POP3Command::QUIT()->send(dynamicCast <POP3Connection>(shared_from_this())); + POP3Response::readResponse(dynamicCast <POP3Connection>(shared_from_this())); + + } catch (exception&) { + + // Not important + } + + m_socket->disconnect(); + } + + m_socket = null; + } + + m_timeoutHandler = null; + + m_authenticated = false; + m_secured = false; + + m_cntInfos = null; +} + + +void POP3Connection::authenticate(const messageId& randomMID) { + + getAuthenticator()->setService(m_store.lock()); + +#if VMIME_HAVE_SASL_SUPPORT + // First, try SASL authentication + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL)) { + + try { + + authenticateSASL(); + + m_authenticated = true; + return; + + } catch (exceptions::authentication_error&) { + + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL_FALLBACK)) { + + // Can't fallback on APOP/normal authentication + internalDisconnect(); + throw; + + } else { + + // Ignore, will try APOP/normal authentication + } + + } catch (exception&) { + + internalDisconnect(); + throw; + } + } +#endif // VMIME_HAVE_SASL_SUPPORT + + // Secured authentication with APOP (if requested and if available) + // + // eg: C: APOP vincent <digest> + // --- S: +OK vincent is a valid mailbox + + const string username = getAuthenticator()->getUsername(); + const string password = getAuthenticator()->getPassword(); + + shared_ptr <POP3Connection> conn = dynamicCast <POP3Connection>(shared_from_this()); + shared_ptr <POP3Response> response; + + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP)) { + + if (randomMID.getLeft().length() != 0 && + randomMID.getRight().length() != 0) { + + // <digest> is the result of MD5 applied to "<message-id>password" + shared_ptr <security::digest::messageDigest> md5 = + security::digest::messageDigestFactory::getInstance()->create("md5"); + + md5->update(randomMID.generate() + password); + md5->finalize(); + + POP3Command::APOP(username, md5->getHexDigest())->send(conn); + response = POP3Response::readResponse(conn); + + if (response->isSuccess()) { + + m_authenticated = true; + return; + + } else { + + // Some servers close the connection after an unsuccessful APOP + // command, so the fallback may not always work... + // + // S: +OK Qpopper (version 4.0.5) at xxx starting. <30396.1126730747@xxx> + // C: APOP plop c5e0a87d088ec71d60e32692d4c5bdf4 + // S: -ERR [AUTH] Password supplied for "plop" is incorrect. + // S: +OK Pop server at xxx signing off. + // [Connection closed by foreign host.] + + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) { + + // Can't fallback on basic authentication + internalDisconnect(); + throw exceptions::authentication_error(response->getFirstLine()); + } + + // Ensure connection is valid (cf. note above) + try { + + POP3Command::NOOP()->send(conn); + POP3Response::readResponse(conn); + + } catch (exceptions::socket_exception&) { + + internalDisconnect(); + throw exceptions::authentication_error(response->getFirstLine()); + } + } + + } else { + + // APOP not supported + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) { + + // Can't fallback on basic authentication + internalDisconnect(); + throw exceptions::authentication_error("APOP not supported"); + } + } + } + + // Basic authentication + // + // eg: C: USER vincent + // --- S: +OK vincent is a valid mailbox + // + // C: PASS couic + // S: +OK vincent's maildrop has 2 messages (320 octets) + POP3Command::USER(username)->send(conn); + response = POP3Response::readResponse(conn); + + if (!response->isSuccess()) { + + internalDisconnect(); + throw exceptions::authentication_error(response->getFirstLine()); + } + + POP3Command::PASS(password)->send(conn); + response = POP3Response::readResponse(conn); + + if (!response->isSuccess()) { + + internalDisconnect(); + throw exceptions::authentication_error(response->getFirstLine()); + } + + m_authenticated = true; +} + + +#if VMIME_HAVE_SASL_SUPPORT + +void POP3Connection::authenticateSASL() { + + if (!dynamicCast <security::sasl::SASLAuthenticator>(getAuthenticator())) { + throw exceptions::authentication_error("No SASL authenticator available."); + } + + std::vector <string> capa = getCapabilities(); + std::vector <string> saslMechs; + + for (unsigned int i = 0 ; i < capa.size() ; ++i) { + + const string& x = capa[i]; + + // C: CAPA + // S: +OK List of capabilities follows + // S: LOGIN-DELAY 0 + // S: PIPELINING + // S: UIDL + // S: ... + // S: SASL DIGEST-MD5 CRAM-MD5 <----- + // S: EXPIRE NEVER + // S: ... + + if (x.length() > 5 && + (x[0] == 'S' || x[0] == 's') && + (x[1] == 'A' || x[1] == 'a') && + (x[2] == 'S' || x[2] == 's') && + (x[3] == 'L' || x[3] == 'l') && + (x[4] == ' ' || x[4] == '\t')) { + + const string list(x.begin() + 5, x.end()); + + std::istringstream iss(list); + iss.imbue(std::locale::classic()); + + string mech; + + while (iss >> mech) { + saslMechs.push_back(mech); + } + } + } + + 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("pop3", getAuthenticator(), mech); + + saslSession->init(); + + shared_ptr <POP3Command> authCmd; + + 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()) { + authCmd = POP3Command::AUTH(mech->getName(), "="); + } else { + authCmd = POP3Command::AUTH(mech->getName(), encodedInitialResp); + } + + } else { + + authCmd = POP3Command::AUTH(mech->getName()); + } + + authCmd->send(dynamicCast <POP3Connection>(shared_from_this())); + + for (bool cont = true ; cont ; ) { + + shared_ptr <POP3Response> response = + POP3Response::readResponse(dynamicCast <POP3Connection>(shared_from_this())); + + switch (response->getCode()) { + + case POP3Response::CODE_OK: { + + m_socket = saslSession->getSecuredSocket(m_socket); + return; + } + + case POP3Response::CODE_READY: { + + 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 POP3Connection::startTLS() { + + try { + + POP3Command::STLS()->send(dynamicCast <POP3Connection>(shared_from_this())); + + shared_ptr <POP3Response> response = + POP3Response::readResponse(dynamicCast <POP3Connection>(shared_from_this())); + + if (!response->isSuccess()) { + throw exceptions::command_error("STLS", response->getFirstLine()); + } + + shared_ptr <tls::TLSSession> tlsSession = tls::TLSSession::create( + m_store.lock()->getCertificateVerifier(), + m_store.lock()->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 + ); + + // " Once TLS has been started, the client MUST discard cached + // information about server capabilities and SHOULD re-issue + // the CAPA command. This is necessary to protect against + // man-in-the-middle attacks which alter the capabilities list + // prior to STLS. " (RFC-2595) + invalidateCapabilities(); + + } catch (exceptions::command_error&) { + + // Non-fatal error + throw; + + } catch (exception&) { + + // Fatal error + internalDisconnect(); + throw; + } +} + +#endif // VMIME_HAVE_TLS_SUPPORT + + +const std::vector <string> POP3Connection::getCapabilities() { + + if (!m_capabilitiesFetched) { + fetchCapabilities(); + } + + return m_capabilities; +} + + +void POP3Connection::invalidateCapabilities() { + + m_capabilities.clear(); + m_capabilitiesFetched = false; +} + + +void POP3Connection::fetchCapabilities() { + + POP3Command::CAPA()->send(dynamicCast <POP3Connection>(shared_from_this())); + + shared_ptr <POP3Response> response = + POP3Response::readMultilineResponse(dynamicCast <POP3Connection>(shared_from_this())); + + std::vector <string> res; + + if (response->isSuccess()) { + + for (size_t i = 0, n = response->getLineCount() ; i < n ; ++i) { + res.push_back(response->getLineAt(i)); + } + } + + m_capabilities = res; + m_capabilitiesFetched = true; +} + + +bool POP3Connection::isConnected() const { + + return m_socket && m_socket->isConnected() && m_authenticated; +} + + +bool POP3Connection::isSecuredConnection() const { + + return m_secured; +} + + +shared_ptr <connectionInfos> POP3Connection::getConnectionInfos() const { + + return m_cntInfos; +} + + +shared_ptr <POP3Store> POP3Connection::getStore() { + + return m_store.lock(); +} + + +shared_ptr <session> POP3Connection::getSession() { + + return m_store.lock()->getSession(); +} + + +shared_ptr <socket> POP3Connection::getSocket() { + + return m_socket; +} + + +shared_ptr <tracer> POP3Connection::getTracer() { + + return m_tracer; +} + + +shared_ptr <timeoutHandler> POP3Connection::getTimeoutHandler() { + + return m_timeoutHandler; +} + + +shared_ptr <security::authenticator> POP3Connection::getAuthenticator() { + + return m_auth; +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 |