diff options
Diffstat (limited to 'src/net/pop3/POP3Connection.cpp')
-rw-r--r-- | src/net/pop3/POP3Connection.cpp | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/src/net/pop3/POP3Connection.cpp b/src/net/pop3/POP3Connection.cpp new file mode 100644 index 00000000..96717620 --- /dev/null +++ b/src/net/pop3/POP3Connection.cpp @@ -0,0 +1,642 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2013 Vincent Richard <[email protected]> +// +// 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.acquire()->getInfos().getPropertyValue <type>(getSession(), \ + dynamic_cast <const POP3ServiceInfos&>(m_store.acquire()->getInfos()).getProperties().prop)) +#define HAS_PROPERTY(prop) \ + (m_store.acquire()->getInfos().hasProperty(getSession(), \ + dynamic_cast <const POP3ServiceInfos&>(m_store.acquire()->getInfos()).getProperties().prop)) + + +namespace vmime { +namespace net { +namespace pop3 { + + + +POP3Connection::POP3Connection(ref <POP3Store> store, ref <security::authenticator> auth) + : m_store(store), m_auth(auth), m_socket(NULL), m_timeoutHandler(NULL), + m_authenticated(false), m_secured(false) +{ +} + + +POP3Connection::~POP3Connection() +{ + try + { + if (isConnected()) + disconnect(); + else if (m_socket) + internalDisconnect(); + } + catch (vmime::exception&) + { + // Ignore + } +} + + +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); + + ref <POP3Store> store = m_store.acquire(); + + // 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); + +#if VMIME_HAVE_TLS_SUPPORT + if (store->isPOP3S()) // dedicated port/POP3S + { + ref <tls::TLSSession> tlsSession = + tls::TLSSession::create(store->getCertificateVerifier()); + + ref <tls::TLSSocket> tlsSocket = + tlsSession->getSocket(m_socket); + + m_socket = tlsSocket; + + m_secured = true; + m_cntInfos = vmime::create <tls::TLSSecuredConnectionInfos>(address, port, tlsSession, tlsSocket); + } + else +#endif // VMIME_HAVE_TLS_SUPPORT + { + m_cntInfos = vmime::create <defaultConnectionInfos>(address, port); + } + + m_socket->connect(address, port); + + // Connection + // + // eg: C: <connection to server> + // --- S: +OK MailSite POP3 Server 5.3.4.0 Ready <[email protected]> + + ref <POP3Response> response = POP3Response::readResponse + (thisRef().dynamicCast <POP3Connection>()); + + 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() +{ + try + { + POP3Command::QUIT()->send(thisRef().dynamicCast <POP3Connection>()); + POP3Response::readResponse(thisRef().dynamicCast <POP3Connection>()); + } + 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(thisRef().dynamicCast <service>()); + +#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& e) + { + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL_FALLBACK)) + { + // Can't fallback on APOP/normal authentication + internalDisconnect(); + throw e; + } + else + { + // Ignore, will try APOP/normal authentication + } + } + catch (exception& e) + { + internalDisconnect(); + throw e; + } + } +#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(); + + ref <POP3Connection> conn = thisRef().dynamicCast <POP3Connection>(); + ref <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" + ref <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 (!getAuthenticator().dynamicCast <security::sasl::SASLAuthenticator>()) + 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); + string mech; + + while (iss >> mech) + saslMechs.push_back(mech); + } + } + + if (saslMechs.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + std::vector <ref <security::sasl::SASLMechanism> > mechList; + + ref <security::sasl::SASLContext> saslContext = + vmime::create <security::sasl::SASLContext>(); + + 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 + ref <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 = getAuthenticator().dynamicCast <security::sasl::SASLAuthenticator>()-> + 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) + { + ref <security::sasl::SASLMechanism> mech = mechList[i]; + + ref <security::sasl::SASLSession> saslSession = + saslContext->createSession("pop3", getAuthenticator(), mech); + + saslSession->init(); + + POP3Command::AUTH(mech->getName())->send(thisRef().dynamicCast <POP3Connection>()); + + for (bool cont = true ; cont ; ) + { + ref <POP3Response> response = + POP3Response::readResponse(thisRef().dynamicCast <POP3Connection>()); + + switch (response->getCode()) + { + case POP3Response::CODE_OK: + { + m_socket = saslSession->getSecuredSocket(m_socket); + return; + } + case POP3Response::CODE_READY: + { + byte_t* challenge = 0; + long challengeLen = 0; + + byte_t* resp = 0; + long respLen = 0; + + try + { + // Extract challenge + saslContext->decodeB64(response->getText(), &challenge, &challengeLen); + + // Prepare response + saslSession->evaluateChallenge + (challenge, challengeLen, &resp, &respLen); + + // Send response + m_socket->send(saslContext->encodeB64(resp, respLen) + "\r\n"); + } + catch (exceptions::sasl_exception& e) + { + if (challenge) + { + delete [] challenge; + challenge = NULL; + } + + if (resp) + { + delete [] resp; + resp = NULL; + } + + // Cancel SASL exchange + m_socket->sendRaw("*\r\n", 3); + } + 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(thisRef().dynamicCast <POP3Connection>()); + + ref <POP3Response> response = + POP3Response::readResponse(thisRef().dynamicCast <POP3Connection>()); + + if (!response->isSuccess()) + throw exceptions::command_error("STLS", response->getFirstLine()); + + ref <tls::TLSSession> tlsSession = + tls::TLSSession::create(m_store.acquire()->getCertificateVerifier()); + + ref <tls::TLSSocket> tlsSocket = + tlsSession->getSocket(m_socket); + + tlsSocket->handshake(m_timeoutHandler); + + m_socket = tlsSocket; + + m_secured = true; + m_cntInfos = vmime::create <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 + + +const std::vector <string> POP3Connection::getCapabilities() +{ + POP3Command::CAPA()->send(thisRef().dynamicCast <POP3Connection>()); + + ref <POP3Response> response = + POP3Response::readMultilineResponse(thisRef().dynamicCast <POP3Connection>()); + + std::vector <string> res; + + if (response->isSuccess()) + { + for (size_t i = 0, n = response->getLineCount() ; i < n ; ++i) + res.push_back(response->getLineAt(i)); + } + + return res; +} + + +bool POP3Connection::isConnected() const +{ + return m_socket && m_socket->isConnected() && m_authenticated; +} + + +bool POP3Connection::isSecuredConnection() const +{ + return m_secured; +} + + +ref <connectionInfos> POP3Connection::getConnectionInfos() const +{ + return m_cntInfos; +} + + +ref <POP3Store> POP3Connection::getStore() +{ + return m_store.acquire(); +} + + +ref <session> POP3Connection::getSession() +{ + return m_store.acquire()->getSession(); +} + + +ref <socket> POP3Connection::getSocket() +{ + return m_socket; +} + + +ref <timeoutHandler> POP3Connection::getTimeoutHandler() +{ + return m_timeoutHandler; +} + + +ref <security::authenticator> POP3Connection::getAuthenticator() +{ + return m_auth; +} + + +} // pop3 +} // net +} // vmime + + +#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_POP3 |