diff options
Diffstat (limited to 'src/vmime/net/imap/IMAPConnection.cpp')
-rw-r--r-- | src/vmime/net/imap/IMAPConnection.cpp | 814 |
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 + |