diff options
Diffstat (limited to 'src/net/pop3/POP3Store.cpp')
-rw-r--r-- | src/net/pop3/POP3Store.cpp | 426 |
1 files changed, 358 insertions, 68 deletions
diff --git a/src/net/pop3/POP3Store.cpp b/src/net/pop3/POP3Store.cpp index 5041d2d3..65973c8f 100644 --- a/src/net/pop3/POP3Store.cpp +++ b/src/net/pop3/POP3Store.cpp @@ -25,6 +25,11 @@ #include "vmime/messageId.hpp" #include "vmime/security/digest/messageDigestFactory.hpp" #include "vmime/utility/filteredStream.hpp" +#include "vmime/utility/stringUtils.hpp" + +#if VMIME_HAVE_SASL_SUPPORT + #include "vmime/security/sasl/SASLContext.hpp" +#endif // VMIME_HAVE_SASL_SUPPORT #include <algorithm> @@ -41,7 +46,7 @@ namespace net { namespace pop3 { -POP3Store::POP3Store(ref <session> sess, ref <authenticator> auth) +POP3Store::POP3Store(ref <session> sess, ref <security::authenticator> auth) : store(sess, getInfosInstance(), auth), m_socket(NULL), m_authentified(false), m_timeoutHandler(NULL) { @@ -50,10 +55,17 @@ POP3Store::POP3Store(ref <session> sess, ref <authenticator> auth) POP3Store::~POP3Store() { - if (isConnected()) - disconnect(); - else if (m_socket) - internalDisconnect(); + try + { + if (isConnected()) + disconnect(); + else if (m_socket) + internalDisconnect(); + } + catch (vmime::exception&) + { + // Ignore + } } @@ -128,98 +140,319 @@ void POP3Store::connect() string response; readResponse(response, false); - if (isSuccessResponse(response)) + if (!isSuccessResponse(response)) { - bool authentified = false; - - const authenticationInfos auth = getAuthenticator()->requestAuthInfos(); + internalDisconnect(); + throw exceptions::connection_greeting_error(response); + } - // Secured authentication with APOP (if requested and if available) - // - // eg: C: APOP vincent <digest> - // --- S: +OK vincent is a valid mailbox - messageId mid(response); + // Start authentication process + authenticate(messageId(response)); +} - if (GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP)) - { - if (mid.getLeft().length() && mid.getRight().length()) - { - // <digest> is the result of MD5 applied to "<message-id>password" - ref <security::digest::messageDigest> md5 = - security::digest::messageDigestFactory::getInstance()->create("md5"); - md5->update(mid.generate() + auth.getPassword()); - md5->finalize(); +void POP3Store::authenticate(const messageId& randomMID) +{ + getAuthenticator()->setService(thisRef().dynamicCast <service>()); - sendRequest("APOP " + auth.getUsername() + " " + md5->getHexDigest()); - readResponse(response, false); +#if VMIME_HAVE_SASL_SUPPORT + // First, try SASL authentication + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL)) + { + try + { + authenticateSASL(); - if (isSuccessResponse(response)) - { - authentified = true; - } - else - { - if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) - { - internalDisconnect(); - throw exceptions::authentication_error(response); - } - } + m_authentified = 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 { - // APOP not supported - if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) - { - // Can't fallback on basic authentification - internalDisconnect(); - throw exceptions::unsupported_option(); - } + // 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(); - if (!authentified) + string response; + + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP)) + { + if (randomMID.getLeft().length() != 0 && + randomMID.getRight().length() != 0) { - // 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) - - sendRequest("USER " + auth.getUsername()); + // <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(); + + sendRequest("APOP " + username + " " + md5->getHexDigest()); readResponse(response, false); if (isSuccessResponse(response)) { - sendRequest("PASS " + auth.getPassword()); - readResponse(response, false); + m_authentified = 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 "o" is incorrect. + // S: +OK Pop server at xxx signing off. + // [Connection closed by foreign host.] - if (!isSuccessResponse(response)) + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) { + // Can't fallback on basic authentication internalDisconnect(); throw exceptions::authentication_error(response); } } - else + } + else + { + // APOP not supported + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) { + // Can't fallback on basic authentication internalDisconnect(); - throw exceptions::authentication_error(response); + throw exceptions::authentication_error("APOP not supported"); } } } - else + + // 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) + sendRequest("USER " + username); + readResponse(response, false); + + if (!isSuccessResponse(response)) { internalDisconnect(); - throw exceptions::connection_greeting_error(response); + throw exceptions::authentication_error(response); + } + + sendRequest("PASS " + password); + readResponse(response, false); + + if (!isSuccessResponse(response)) + { + internalDisconnect(); + throw exceptions::authentication_error(response); } m_authentified = true; } +#if VMIME_HAVE_SASL_SUPPORT + +void POP3Store::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') && + std::isspace(x[4])) + { + 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(); + + sendRequest("AUTH " + mech->getName()); + + for (bool cont = true ; cont ; ) + { + string response; + readResponse(response, false); + + switch (getResponseCode(response)) + { + case RESPONSE_OK: + { + m_socket = saslSession->getSecuredSocket(m_socket); + return; + } + case RESPONSE_READY: + { + byte* challenge = 0; + int challengeLen = 0; + + byte* resp = 0; + int respLen = 0; + + try + { + // Extract challenge + stripResponseCode(response, response); + saslContext->decodeB64(response, &challenge, &challengeLen); + + // Prepare response + saslSession->evaluateChallenge + (challenge, challengeLen, &resp, &respLen); + + // Send response + sendRequest(saslContext->encodeB64(resp, respLen)); + } + catch (exceptions::sasl_exception& e) + { + if (challenge) + { + delete [] challenge; + challenge = NULL; + } + + if (resp) + { + delete [] resp; + resp = NULL; + } + + // Cancel SASL exchange + sendRequest("*"); + } + 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 + + const bool POP3Store::isConnected() const { return (m_socket && m_socket->isConnected() && m_authentified); @@ -259,7 +492,7 @@ void POP3Store::internalDisconnect() void POP3Store::noop() { - m_socket->send("NOOP"); + sendRequest("NOOP"); string response; readResponse(response, false); @@ -269,12 +502,33 @@ void POP3Store::noop() } -const bool POP3Store::isSuccessResponse(const string& buffer) +const std::vector <string> POP3Store::getCapabilities() { - static const string OK("+OK"); + sendRequest("CAPA"); + + string response; + readResponse(response, true); + + std::vector <string> res; - return (buffer.length() >= 3 && - std::equal(buffer.begin(), buffer.begin() + 3, OK.begin())); + if (isSuccessResponse(response)) + { + stripFirstLine(response, response); + + std::istringstream iss(response); + string line; + + while (std::getline(iss, line, '\n')) + res.push_back(utility::stringUtils::trim(line)); + } + + return res; +} + + +const bool POP3Store::isSuccessResponse(const string& buffer) +{ + return getResponseCode(buffer) == RESPONSE_OK; } @@ -296,6 +550,34 @@ const bool POP3Store::stripFirstLine(const string& buffer, string& result, strin } +const int POP3Store::getResponseCode(const string& buffer) +{ + if (buffer.length() >= 2) + { + // +[space] + if (buffer[0] == '+' && + (buffer[1] == ' ' || buffer[1] == '\t')) + { + return RESPONSE_READY; + } + + // +OK + if (buffer.length() >= 3) + { + if (buffer[0] == '+' && + (buffer[1] == 'O' || buffer[1] == 'o') && + (buffer[2] == 'K' || buffer[1] == 'k')) + { + return RESPONSE_OK; + } + } + } + + // -ERR or whatever + return RESPONSE_ERR; +} + + void POP3Store::stripResponseCode(const string& buffer, string& result) { const string::size_type pos = buffer.find_first_of(" \t"); @@ -588,8 +870,12 @@ const POP3Store::_infos::props& POP3Store::_infos::getProperties() const static props p = { // POP3-specific options - property("options.apop", serviceInfos::property::TYPE_BOOL, "false"), - property("options.apop.fallback", serviceInfos::property::TYPE_BOOL, "false"), + property("options.apop", serviceInfos::property::TYPE_BOOL, "true"), + property("options.apop.fallback", serviceInfos::property::TYPE_BOOL, "true"), +#if VMIME_HAVE_SASL_SUPPORT + property("options.sasl", serviceInfos::property::TYPE_BOOL, "true"), + property("options.sasl.fallback", serviceInfos::property::TYPE_BOOL, "true"), +#endif // VMIME_HAVE_SASL_SUPPORT // Common properties property(serviceInfos::property::AUTH_USERNAME, serviceInfos::property::FLAG_REQUIRED), @@ -614,6 +900,10 @@ const std::vector <serviceInfos::property> POP3Store::_infos::getAvailableProper // POP3-specific options list.push_back(p.PROPERTY_OPTIONS_APOP); list.push_back(p.PROPERTY_OPTIONS_APOP_FALLBACK); +#if VMIME_HAVE_SASL_SUPPORT + list.push_back(p.PROPERTY_OPTIONS_SASL); + list.push_back(p.PROPERTY_OPTIONS_SASL_FALLBACK); +#endif // VMIME_HAVE_SASL_SUPPORT // Common properties list.push_back(p.PROPERTY_AUTH_USERNAME); |