aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/pop3/POP3Connection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/pop3/POP3Connection.cpp')
-rw-r--r--src/net/pop3/POP3Connection.cpp642
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