diff options
author | Wojtek Kosior <wk@koszkonutek-tmp.pl.eu.org> | 2021-04-30 00:33:56 +0200 |
---|---|---|
committer | Wojtek Kosior <wk@koszkonutek-tmp.pl.eu.org> | 2021-04-30 00:33:56 +0200 |
commit | aa4d426b4d3527d7e166df1a05058c9a4a0f6683 (patch) | |
tree | 4ff17ce8b89a2321b9d0ed4bcfc37c447bcb6820 /vmime-master/src/vmime/net/imap/IMAPConnection.cpp | |
download | smtps-and-pop3s-console-program-master.tar.gz smtps-and-pop3s-console-program-master.zip |
Diffstat (limited to 'vmime-master/src/vmime/net/imap/IMAPConnection.cpp')
-rw-r--r-- | vmime-master/src/vmime/net/imap/IMAPConnection.cpp | 886 |
1 files changed, 886 insertions, 0 deletions
diff --git a/vmime-master/src/vmime/net/imap/IMAPConnection.cpp b/vmime-master/src/vmime/net/imap/IMAPConnection.cpp new file mode 100644 index 0000000..df3da1c --- /dev/null +++ b/vmime-master/src/vmime/net/imap/IMAPConnection.cpp @@ -0,0 +1,886 @@ +// +// 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 + |