aboutsummaryrefslogtreecommitdiffstats
path: root/src/vmime/net/imap/IMAPConnection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vmime/net/imap/IMAPConnection.cpp')
-rw-r--r--src/vmime/net/imap/IMAPConnection.cpp814
1 files changed, 814 insertions, 0 deletions
diff --git a/src/vmime/net/imap/IMAPConnection.cpp b/src/vmime/net/imap/IMAPConnection.cpp
new file mode 100644
index 00000000..234c2b6a
--- /dev/null
+++ b/src/vmime/net/imap/IMAPConnection.cpp
@@ -0,0 +1,814 @@
+//
+// 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_IMAP
+
+
+#include "vmime/net/imap/IMAPTag.hpp"
+#include "vmime/net/imap/IMAPConnection.hpp"
+#include "vmime/net/imap/IMAPUtils.hpp"
+#include "vmime/net/imap/IMAPStore.hpp"
+
+#include "vmime/exception.hpp"
+#include "vmime/platform.hpp"
+
+#include "vmime/utility/stringUtils.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
+
+#include <sstream>
+
+
+// Helpers for service properties
+#define GET_PROPERTY(type, prop) \
+ (m_store.lock()->getInfos().getPropertyValue <type>(getSession(), \
+ dynamic_cast <const IMAPServiceInfos&>(m_store.lock()->getInfos()).getProperties().prop))
+#define HAS_PROPERTY(prop) \
+ (m_store.lock()->getInfos().hasProperty(getSession(), \
+ dynamic_cast <const IMAPServiceInfos&>(m_store.lock()->getInfos()).getProperties().prop))
+
+
+namespace vmime {
+namespace net {
+namespace imap {
+
+
+IMAPConnection::IMAPConnection(shared_ptr <IMAPStore> store, shared_ptr <security::authenticator> auth)
+ : m_store(store), m_auth(auth), m_socket(null), m_parser(null), m_tag(null),
+ m_hierarchySeparator('\0'), m_state(STATE_NONE), m_timeoutHandler(null),
+ m_secured(false), m_firstTag(true), m_capabilitiesFetched(false), m_noModSeq(false)
+{
+}
+
+
+IMAPConnection::~IMAPConnection()
+{
+ try
+ {
+ if (isConnected())
+ disconnect();
+ else if (m_socket)
+ internalDisconnect();
+ }
+ catch (vmime::exception&)
+ {
+ // Ignore
+ }
+}
+
+
+void IMAPConnection::connect()
+{
+ if (isConnected())
+ throw exceptions::already_connected();
+
+ m_state = STATE_NONE;
+ m_hierarchySeparator = '\0';
+
+ const string address = GET_PROPERTY(string, PROPERTY_SERVER_ADDRESS);
+ const port_t port = GET_PROPERTY(port_t, PROPERTY_SERVER_PORT);
+
+ shared_ptr <IMAPStore> 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);
+
+#if VMIME_HAVE_TLS_SUPPORT
+ if (store->isIMAPS()) // dedicated port/IMAPS
+ {
+ 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);
+
+
+ m_tag = make_shared <IMAPTag>();
+ m_parser = make_shared <IMAPParser>(m_tag, m_socket, m_timeoutHandler);
+
+
+ setState(STATE_NON_AUTHENTICATED);
+
+
+ // Connection greeting
+ //
+ // eg: C: <connection to server>
+ // --- S: * OK mydomain.org IMAP4rev1 v12.256 server ready
+
+ std::auto_ptr <IMAPParser::greeting> greet(m_parser->readGreeting());
+ bool needAuth = false;
+
+ if (greet->resp_cond_bye())
+ {
+ internalDisconnect();
+ throw exceptions::connection_greeting_error(greet->getErrorLog());
+ }
+ else if (greet->resp_cond_auth()->condition() != IMAPParser::resp_cond_auth::PREAUTH)
+ {
+ needAuth = true;
+ }
+
+ if (greet->resp_cond_auth()->resp_text()->resp_text_code() &&
+ greet->resp_cond_auth()->resp_text()->resp_text_code()->capability_data())
+ {
+ processCapabilityResponseData(greet->resp_cond_auth()->resp_text()->resp_text_code()->capability_data());
+ }
+
+#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->isIMAPS() && tls) // only if not IMAPS
+ {
+ try
+ {
+ startTLS();
+ }
+ // Non-fatal error
+ catch (exceptions::command_error&)
+ {
+ if (tlsRequired)
+ {
+ m_state = STATE_NONE;
+ throw;
+ }
+ else
+ {
+ // TLS is not required, so don't bother
+ }
+ }
+ // Fatal error
+ catch (...)
+ {
+ m_state = STATE_NONE;
+ throw;
+ }
+ }
+#endif // VMIME_HAVE_TLS_SUPPORT
+
+ // Authentication
+ if (needAuth)
+ {
+ try
+ {
+ authenticate();
+ }
+ catch (...)
+ {
+ m_state = STATE_NONE;
+ throw;
+ }
+ }
+
+ // Get the hierarchy separator character
+ initHierarchySeparator();
+
+ // Switch to state "Authenticated"
+ setState(STATE_AUTHENTICATED);
+}
+
+
+void IMAPConnection::authenticate()
+{
+ getAuthenticator()->setService(m_store.lock());
+
+#if VMIME_HAVE_SASL_SUPPORT
+ // First, try SASL authentication
+ if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL))
+ {
+ try
+ {
+ authenticateSASL();
+ 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
+
+ // Normal authentication
+ const string username = getAuthenticator()->getUsername();
+ const string password = getAuthenticator()->getPassword();
+
+ send(true, "LOGIN " + IMAPUtils::quoteString(username)
+ + " " + IMAPUtils::quoteString(password), true);
+
+ std::auto_ptr <IMAPParser::response> resp(m_parser->readResponse());
+
+ if (resp->isBad())
+ {
+ internalDisconnect();
+ throw exceptions::command_error("LOGIN", resp->getErrorLog());
+ }
+ else if (resp->response_done()->response_tagged()->
+ resp_cond_state()->status() != IMAPParser::resp_cond_state::OK)
+ {
+ internalDisconnect();
+ throw exceptions::authentication_error(resp->getErrorLog());
+ }
+
+ // Server capabilities may change when logged in
+ if (!processCapabilityResponseData(resp.get()))
+ invalidateCapabilities();
+}
+
+
+#if VMIME_HAVE_SASL_SUPPORT
+
+void IMAPConnection::authenticateSASL()
+{
+ if (!dynamicCast <security::sasl::SASLAuthenticator>(getAuthenticator()))
+ throw exceptions::authentication_error("No SASL authenticator available.");
+
+ const std::vector <string> capa = getCapabilities();
+ std::vector <string> saslMechs;
+
+ for (unsigned int i = 0 ; i < capa.size() ; ++i)
+ {
+ const string& x = capa[i];
+
+ if (x.length() > 5 &&
+ (x[0] == 'A' || x[0] == 'a') &&
+ (x[1] == 'U' || x[1] == 'u') &&
+ (x[2] == 'T' || x[2] == 't') &&
+ (x[3] == 'H' || x[3] == 'h') &&
+ x[4] == '=')
+ {
+ saslMechs.push_back(string(x.begin() + 5, x.end()));
+ }
+ }
+
+ 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 =
+ make_shared <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
+ 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("imap", getAuthenticator(), mech);
+
+ saslSession->init();
+
+ send(true, "AUTHENTICATE " + mech->getName(), true);
+
+ for (bool cont = true ; cont ; )
+ {
+ std::auto_ptr <IMAPParser::response> resp(m_parser->readResponse());
+
+ if (resp->response_done() &&
+ resp->response_done()->response_tagged() &&
+ resp->response_done()->response_tagged()->resp_cond_state()->
+ status() == IMAPParser::resp_cond_state::OK)
+ {
+ m_socket = saslSession->getSecuredSocket(m_socket);
+ return;
+ }
+ else
+ {
+ std::vector <IMAPParser::continue_req_or_response_data*>
+ respDataList = resp->continue_req_or_response_data();
+
+ string response;
+ bool hasResponse = false;
+
+ for (unsigned int i = 0 ; i < respDataList.size() ; ++i)
+ {
+ if (respDataList[i]->continue_req())
+ {
+ response = respDataList[i]->continue_req()->resp_text()->text();
+ hasResponse = true;
+ break;
+ }
+ }
+
+ if (!hasResponse)
+ {
+ cont = false;
+ continue;
+ }
+
+ byte_t* challenge = 0;
+ size_t challengeLen = 0;
+
+ byte_t* resp = 0;
+ size_t respLen = 0;
+
+ try
+ {
+ // Extract challenge
+ saslContext->decodeB64(response, &challenge, &challengeLen);
+
+ // Prepare response
+ saslSession->evaluateChallenge
+ (challenge, challengeLen, &resp, &respLen);
+
+ // Send response
+ send(false, saslContext->encodeB64(resp, respLen), true);
+
+ // Server capabilities may change when logged in
+ invalidateCapabilities();
+ }
+ catch (exceptions::sasl_exception& e)
+ {
+ if (challenge)
+ {
+ delete [] challenge;
+ challenge = NULL;
+ }
+
+ if (resp)
+ {
+ delete [] resp;
+ resp = NULL;
+ }
+
+ // Cancel SASL exchange
+ send(false, "*", true);
+ }
+ catch (...)
+ {
+ if (challenge)
+ delete [] challenge;
+
+ if (resp)
+ delete [] resp;
+
+ throw;
+ }
+
+ if (challenge)
+ delete [] challenge;
+
+ if (resp)
+ delete [] resp;
+ }
+ }
+ }
+
+ throw exceptions::authentication_error
+ ("Could not authenticate using SASL: all mechanisms failed.");
+}
+
+#endif // VMIME_HAVE_SASL_SUPPORT
+
+
+#if VMIME_HAVE_TLS_SUPPORT
+
+void IMAPConnection::startTLS()
+{
+ try
+ {
+ send(true, "STARTTLS", true);
+
+ std::auto_ptr <IMAPParser::response> resp(m_parser->readResponse());
+
+ if (resp->isBad() || resp->response_done()->response_tagged()->
+ resp_cond_state()->status() != IMAPParser::resp_cond_state::OK)
+ {
+ throw exceptions::command_error
+ ("STARTTLS", resp->getErrorLog(), "bad response");
+ }
+
+ 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_timeoutHandler);
+
+ m_socket = tlsSocket;
+ m_parser->setSocket(m_socket);
+
+ 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
+ // CAPABILITY command. This is necessary to protect against
+ // man-in-the-middle attacks which alter the capabilities list prior
+ // to STARTTLS. " (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> IMAPConnection::getCapabilities()
+{
+ if (!m_capabilitiesFetched)
+ fetchCapabilities();
+
+ return m_capabilities;
+}
+
+
+bool IMAPConnection::hasCapability(const string& capa)
+{
+ if (!m_capabilitiesFetched)
+ fetchCapabilities();
+
+ const string normCapa = utility::stringUtils::toUpper(capa);
+
+ for (size_t i = 0, n = m_capabilities.size() ; i < n ; ++i)
+ {
+ if (m_capabilities[i] == normCapa)
+ return true;
+ }
+
+ return false;
+}
+
+
+void IMAPConnection::invalidateCapabilities()
+{
+ m_capabilities.clear();
+ m_capabilitiesFetched = false;
+}
+
+
+void IMAPConnection::fetchCapabilities()
+{
+ send(true, "CAPABILITY", true);
+
+ std::auto_ptr <IMAPParser::response> resp(m_parser->readResponse());
+
+ if (resp->response_done()->response_tagged()->
+ resp_cond_state()->status() == IMAPParser::resp_cond_state::OK)
+ {
+ processCapabilityResponseData(resp.get());
+ }
+}
+
+
+bool IMAPConnection::processCapabilityResponseData(const IMAPParser::response* resp)
+{
+ const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList =
+ resp->continue_req_or_response_data();
+
+ for (size_t i = 0 ; i < respDataList.size() ; ++i)
+ {
+ if (respDataList[i]->response_data() == NULL)
+ continue;
+
+ const IMAPParser::capability_data* capaData =
+ respDataList[i]->response_data()->capability_data();
+
+ if (capaData == NULL)
+ continue;
+
+ processCapabilityResponseData(capaData);
+ return true;
+ }
+
+ return false;
+}
+
+
+void IMAPConnection::processCapabilityResponseData(const IMAPParser::capability_data* capaData)
+{
+ std::vector <string> res;
+
+ std::vector <IMAPParser::capability*> caps = capaData->capabilities();
+
+ for (unsigned int j = 0 ; j < caps.size() ; ++j)
+ {
+ if (caps[j]->auth_type())
+ res.push_back("AUTH=" + caps[j]->auth_type()->name());
+ else
+ res.push_back(utility::stringUtils::toUpper(caps[j]->atom()->value()));
+ }
+
+ m_capabilities = res;
+ m_capabilitiesFetched = true;
+}
+
+
+shared_ptr <security::authenticator> IMAPConnection::getAuthenticator()
+{
+ return m_auth;
+}
+
+
+bool IMAPConnection::isConnected() const
+{
+ return (m_socket && m_socket->isConnected() &&
+ (m_state == STATE_AUTHENTICATED || m_state == STATE_SELECTED));
+}
+
+
+bool IMAPConnection::isSecuredConnection() const
+{
+ return m_secured;
+}
+
+
+shared_ptr <connectionInfos> IMAPConnection::getConnectionInfos() const
+{
+ return m_cntInfos;
+}
+
+
+void IMAPConnection::disconnect()
+{
+ if (!isConnected())
+ throw exceptions::not_connected();
+
+ internalDisconnect();
+}
+
+
+void IMAPConnection::internalDisconnect()
+{
+ if (isConnected())
+ {
+ send(true, "LOGOUT", true);
+
+ m_socket->disconnect();
+ m_socket = null;
+ }
+
+ m_timeoutHandler = null;
+
+ m_state = STATE_LOGOUT;
+
+ m_secured = false;
+ m_cntInfos = null;
+}
+
+
+void IMAPConnection::initHierarchySeparator()
+{
+ send(true, "LIST \"\" \"\"", true);
+
+ std::auto_ptr <IMAPParser::response> resp(m_parser->readResponse());
+
+ if (resp->isBad() || resp->response_done()->response_tagged()->
+ resp_cond_state()->status() != IMAPParser::resp_cond_state::OK)
+ {
+ internalDisconnect();
+ throw exceptions::command_error("LIST", resp->getErrorLog(), "bad response");
+ }
+
+ const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList =
+ resp->continue_req_or_response_data();
+
+ bool found = false;
+
+ for (unsigned int i = 0 ; !found && i < respDataList.size() ; ++i)
+ {
+ if (respDataList[i]->response_data() == NULL)
+ continue;
+
+ const IMAPParser::mailbox_data* mailboxData =
+ static_cast <const IMAPParser::response_data*>
+ (respDataList[i]->response_data())->mailbox_data();
+
+ if (mailboxData == NULL || mailboxData->type() != IMAPParser::mailbox_data::LIST)
+ continue;
+
+ if (mailboxData->mailbox_list()->quoted_char() != '\0')
+ {
+ m_hierarchySeparator = mailboxData->mailbox_list()->quoted_char();
+ found = true;
+ }
+ }
+
+ if (!found) // default
+ m_hierarchySeparator = '/';
+}
+
+
+void IMAPConnection::send(bool tag, const string& what, bool end)
+{
+ if (tag && !m_firstTag)
+ ++(*m_tag);
+
+#if VMIME_DEBUG
+ std::ostringstream oss;
+
+ if (tag)
+ {
+ oss << string(*m_tag);
+ oss << " ";
+ }
+
+ oss << what;
+
+ if (end)
+ oss << "\r\n";
+
+ m_socket->send(oss.str());
+#else
+ if (tag)
+ {
+ m_socket->send(*m_tag);
+ m_socket->send(" ");
+ }
+
+ m_socket->send(what);
+
+ if (end)
+ {
+ m_socket->send("\r\n");
+ }
+#endif
+
+ if (tag)
+ m_firstTag = false;
+}
+
+
+void IMAPConnection::sendRaw(const byte_t* buffer, const size_t count)
+{
+ m_socket->sendRaw(buffer, count);
+}
+
+
+IMAPParser::response* IMAPConnection::readResponse(IMAPParser::literalHandler* lh)
+{
+ return (m_parser->readResponse(lh));
+}
+
+
+IMAPConnection::ProtocolStates IMAPConnection::state() const
+{
+ return (m_state);
+}
+
+
+void IMAPConnection::setState(const ProtocolStates state)
+{
+ m_state = state;
+}
+
+
+char IMAPConnection::hierarchySeparator() const
+{
+ return (m_hierarchySeparator);
+}
+
+
+shared_ptr <const IMAPStore> IMAPConnection::getStore() const
+{
+ return m_store.lock();
+}
+
+
+shared_ptr <IMAPStore> IMAPConnection::getStore()
+{
+ return m_store.lock();
+}
+
+
+shared_ptr <session> IMAPConnection::getSession()
+{
+ return m_store.lock()->getSession();
+}
+
+
+shared_ptr <const socket> IMAPConnection::getSocket() const
+{
+ return m_socket;
+}
+
+
+bool IMAPConnection::isMODSEQDisabled() const
+{
+ return m_noModSeq;
+}
+
+
+void IMAPConnection::disableMODSEQ()
+{
+ m_noModSeq = true;
+}
+
+
+} // imap
+} // net
+} // vmime
+
+
+#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_IMAP
+