// // 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_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/net/imap/IMAPCommand.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( const shared_ptr <IMAPStore>& store, const 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) { static int connectionId = 0; m_tag = make_shared <IMAPTag>(); if (store->getTracerFactory()) { m_tracer = store->getTracerFactory()->create(store, ++connectionId); } m_parser = make_shared <IMAPParser>(); m_parser->setTracer(m_tracer); } IMAPConnection::~IMAPConnection() { try { if (isConnected()) { disconnect(); } else if (m_socket) { internalDisconnect(); } } catch (...) { // Don't throw in destructor } } 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); m_socket->setTracer(m_tracer); #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_parser->setSocket(m_socket); m_parser->setTimeoutHandler(m_timeoutHandler); setState(STATE_NON_AUTHENTICATED); // Connection greeting // // eg: C: <connection to server> // --- S: * OK mydomain.org IMAP4rev1 v12.256 server ready scoped_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.get()); } #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&) { if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL_FALLBACK)) { // Can't fallback on normal authentication internalDisconnect(); throw; } else { // Ignore, will try normal authentication } } catch (exception&) { internalDisconnect(); throw; } } #endif // VMIME_HAVE_SASL_SUPPORT // Normal authentication const string username = getAuthenticator()->getUsername(); const string password = getAuthenticator()->getPassword(); shared_ptr <IMAPConnection> conn = dynamicCast <IMAPConnection>(shared_from_this()); IMAPCommand::LOGIN(username, password)->send(conn); scoped_ptr <IMAPParser::response> resp(m_parser->readResponse(*m_tag)); 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 = 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("imap", getAuthenticator(), mech); saslSession->init(); shared_ptr <IMAPCommand> 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 = IMAPCommand::AUTHENTICATE(mech->getName(), "="); } else { authCmd = IMAPCommand::AUTHENTICATE(mech->getName(), encodedInitialResp); } } else { authCmd = IMAPCommand::AUTHENTICATE(mech->getName()); } authCmd->send(dynamicCast <IMAPConnection>(shared_from_this())); for (bool cont = true ; cont ; ) { scoped_ptr <IMAPParser::response> resp(m_parser->readResponse(*m_tag)); 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 { string response; bool hasResponse = false; for (auto &respData : resp->continue_req_or_response_data) { if (respData->continue_req) { response = respData->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 const string respB64 = saslContext->encodeB64(resp, respLen) + "\r\n"; sendRaw(utility::stringUtils::bytesFromString(respB64), respB64.length()); if (m_tracer) { m_tracer->traceSendBytes(respB64.length() - 2, "SASL exchange"); } // 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 sendRaw(utility::stringUtils::bytesFromString("*\r\n"), 3); if (m_tracer) { m_tracer->traceSend("*"); } } 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 { IMAPCommand::STARTTLS()->send(dynamicCast <IMAPConnection>(shared_from_this())); scoped_ptr <IMAPParser::response> resp(m_parser->readResponse(*m_tag)); 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_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; } bool IMAPConnection::hasCapability(const string& capa) const { 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() { IMAPCommand::CAPABILITY()->send(dynamicCast <IMAPConnection>(shared_from_this())); scoped_ptr <IMAPParser::response> resp(m_parser->readResponse(*m_tag)); 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) { for (auto &respData : resp->continue_req_or_response_data) { if (respData->response_data == NULL) { continue; } auto &capaData = respData->response_data->capability_data; if (!capaData) { continue; } processCapabilityResponseData(capaData.get()); return true; } return false; } void IMAPConnection::processCapabilityResponseData(const IMAPParser::capability_data* capaData) { std::vector <string> res; for (auto &cap : capaData->capabilities) { if (cap->auth_type) { res.push_back("AUTH=" + cap->auth_type->name); } else { res.push_back(utility::stringUtils::toUpper(cap->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()) { IMAPCommand::LOGOUT()->send(dynamicCast <IMAPConnection>(shared_from_this())); m_socket->disconnect(); m_socket = null; } m_timeoutHandler = null; m_state = STATE_LOGOUT; m_secured = false; m_cntInfos = null; } void IMAPConnection::initHierarchySeparator() { IMAPCommand::LIST("", "")->send(dynamicCast <IMAPConnection>(shared_from_this())); scoped_ptr <IMAPParser::response> resp(m_parser->readResponse(*m_tag)); 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 auto& 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) { continue; } auto &mailboxData = respDataList[i]->response_data->mailbox_data; if (!mailboxData || 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::sendCommand(const shared_ptr <IMAPCommand>& cmd) { if (!m_firstTag) { ++(*m_tag); } m_socket->send(*m_tag); m_socket->send(" "); m_socket->send(cmd->getText()); m_socket->send("\r\n"); m_firstTag = false; if (m_tracer) { std::ostringstream oss; oss << string(*m_tag) << " " << cmd->getText(); m_tracer->traceSend(oss.str()); } } 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(*m_tag, 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; } void IMAPConnection::setSocket(const shared_ptr <socket>& sok) { m_socket = sok; m_parser->setSocket(sok); } shared_ptr <tracer> IMAPConnection::getTracer() { return m_tracer; } shared_ptr <IMAPTag> IMAPConnection::getTag() { return m_tag; } 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