diff options
Diffstat (limited to '')
-rw-r--r-- | src/net/authenticationInfos.cpp | 52 | ||||
-rw-r--r-- | src/net/authenticator.cpp | 33 | ||||
-rw-r--r-- | src/net/defaultAuthenticator.cpp | 43 | ||||
-rw-r--r-- | src/net/imap/IMAPConnection.cpp | 312 | ||||
-rw-r--r-- | src/net/imap/IMAPFolder.cpp | 2 | ||||
-rw-r--r-- | src/net/imap/IMAPStore.cpp | 80 | ||||
-rw-r--r-- | src/net/maildir/maildirStore.cpp | 15 | ||||
-rw-r--r-- | src/net/pop3/POP3Store.cpp | 426 | ||||
-rw-r--r-- | src/net/sendmail/sendmailTransport.cpp | 13 | ||||
-rw-r--r-- | src/net/service.cpp | 29 | ||||
-rw-r--r-- | src/net/serviceFactory.cpp | 6 | ||||
-rw-r--r-- | src/net/session.cpp | 16 | ||||
-rw-r--r-- | src/net/simpleAuthenticator.cpp | 69 | ||||
-rw-r--r-- | src/net/smtp/SMTPTransport.cpp | 406 | ||||
-rw-r--r-- | src/net/transport.cpp | 2 |
15 files changed, 1014 insertions, 490 deletions
diff --git a/src/net/authenticationInfos.cpp b/src/net/authenticationInfos.cpp deleted file mode 100644 index e5a93fb2..00000000 --- a/src/net/authenticationInfos.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// -// VMime library (http://www.vmime.org) -// Copyright (C) 2002-2005 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. -// - -#include "vmime/net/authenticationInfos.hpp" - - -namespace vmime { -namespace net { - - -authenticationInfos::authenticationInfos(const string& username, const string& password) - : m_username(username), m_password(password) -{ -} - - -authenticationInfos::authenticationInfos(const authenticationInfos& infos) - : object(), m_username(infos.m_username), m_password(infos.m_password) -{ -} - - -const string& authenticationInfos::getUsername() const -{ - return (m_username); -} - - -const string& authenticationInfos::getPassword() const -{ - return (m_password); -} - - -} // net -} // vmime diff --git a/src/net/authenticator.cpp b/src/net/authenticator.cpp deleted file mode 100644 index d894915c..00000000 --- a/src/net/authenticator.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// VMime library (http://www.vmime.org) -// Copyright (C) 2002-2005 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. -// - -#include "vmime/net/authenticator.hpp" - - -namespace vmime { -namespace net { - - -authenticator::~authenticator() -{ -} - - -} // net -} // vmime diff --git a/src/net/defaultAuthenticator.cpp b/src/net/defaultAuthenticator.cpp deleted file mode 100644 index 59835b64..00000000 --- a/src/net/defaultAuthenticator.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// -// VMime library (http://www.vmime.org) -// Copyright (C) 2002-2005 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. -// - -#include "vmime/net/defaultAuthenticator.hpp" -#include "vmime/net/session.hpp" - - -namespace vmime { -namespace net { - - -defaultAuthenticator::defaultAuthenticator(weak_ref <session> sess, const string& prefix) - : m_session(sess), m_prefix(prefix) -{ -} - - -const authenticationInfos defaultAuthenticator::requestAuthInfos() const -{ - return (authenticationInfos - (m_session->getProperties()[m_prefix + "auth.username"], - m_session->getProperties()[m_prefix + "auth.password"])); -} - - -} // net -} // vmime diff --git a/src/net/imap/IMAPConnection.cpp b/src/net/imap/IMAPConnection.cpp index 5191dc94..02b6c607 100644 --- a/src/net/imap/IMAPConnection.cpp +++ b/src/net/imap/IMAPConnection.cpp @@ -25,6 +25,10 @@ #include "vmime/exception.hpp" #include "vmime/platformDependant.hpp" +#if VMIME_HAVE_SASL_SUPPORT + #include "vmime/security/sasl/SASLContext.hpp" +#endif // VMIME_HAVE_SASL_SUPPORT + #include <sstream> @@ -42,7 +46,7 @@ namespace net { namespace imap { -IMAPConnection::IMAPConnection(weak_ref <IMAPStore> store, ref <authenticator> auth) +IMAPConnection::IMAPConnection(weak_ref <IMAPStore> store, ref <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) { @@ -51,10 +55,17 @@ IMAPConnection::IMAPConnection(weak_ref <IMAPStore> store, ref <authenticator> a IMAPConnection::~IMAPConnection() { - if (isConnected()) - disconnect(); - else if (m_socket) - internalDisconnect(); + try + { + if (isConnected()) + disconnect(); + else if (m_socket) + internalDisconnect(); + } + catch (vmime::exception&) + { + // Ignore + } } @@ -107,33 +118,294 @@ void IMAPConnection::connect() } else if (greet->resp_cond_auth()->condition() != IMAPParser::resp_cond_auth::PREAUTH) { - const authenticationInfos auth = m_auth->requestAuthInfos(); + try + { + authenticate(); + } + catch (...) + { + m_state = STATE_NONE; + throw; + } + } + + // Get the hierarchy separator character + initHierarchySeparator(); - // TODO: other authentication methods + // Switch to state "Authenticated" + setState(STATE_AUTHENTICATED); +} - send(true, "LOGIN " + IMAPUtils::quoteString(auth.getUsername()) - + " " + IMAPUtils::quoteString(auth.getPassword()), true); - utility::auto_ptr <IMAPParser::response> resp(m_parser->readResponse()); +void IMAPConnection::authenticate() +{ + getAuthenticator()->setService(thisRef().dynamicCast <service>()); - if (resp->isBad()) +#if VMIME_HAVE_SASL_SUPPORT + // First, try SASL authentication + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL)) + { + try { - internalDisconnect(); - throw exceptions::command_error("LOGIN", m_parser->lastLine()); + 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 + } } - else if (resp->response_done()->response_tagged()-> - resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + catch (exception& e) { internalDisconnect(); - throw exceptions::authentication_error(m_parser->lastLine()); + throw e; } } +#endif // VMIME_HAVE_SASL_SUPPORT - // Get the hierarchy separator character - initHierarchySeparator(); + // Normal authentication + const string username = getAuthenticator()->getUsername(); + const string password = getAuthenticator()->getPassword(); - // Switch to state "Authenticated" - setState(STATE_AUTHENTICATED); + send(true, "LOGIN " + IMAPUtils::quoteString(username) + + " " + IMAPUtils::quoteString(password), true); + + utility::auto_ptr <IMAPParser::response> resp(m_parser->readResponse()); + + if (resp->isBad()) + { + internalDisconnect(); + throw exceptions::command_error("LOGIN", m_parser->lastLine()); + } + else if (resp->response_done()->response_tagged()-> + resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + { + internalDisconnect(); + throw exceptions::authentication_error(m_parser->lastLine()); + } +} + + +#if VMIME_HAVE_SASL_SUPPORT + +void IMAPConnection::authenticateSASL() +{ + if (!getAuthenticator().dynamicCast <security::sasl::SASLAuthenticator>()) + 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 <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("imap", getAuthenticator(), mech); + + saslSession->init(); + + send(true, "AUTHENTICATE " + mech->getName(), true); + + for (bool cont = true ; cont ; ) + { + utility::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; + + for (unsigned int i = 0 ; i < respDataList.size() ; ++i) + { + if (respDataList[i]->continue_req()) + { + response = respDataList[i]->continue_req()->resp_text()->text(); + break; + } + } + + if (response.empty()) + { + cont = false; + continue; + } + + byte* challenge = 0; + int challengeLen = 0; + + byte* resp = 0; + int 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); + } + 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 + + +const std::vector <string> IMAPConnection::getCapabilities() +{ + send(true, "CAPABILITY", true); + + utility::auto_ptr <IMAPParser::response> resp(m_parser->readResponse()); + + std::vector <string> res; + + if (resp->response_done()->response_tagged()-> + resp_cond_state()->status() == IMAPParser::resp_cond_state::OK) + { + const std::vector <IMAPParser::continue_req_or_response_data*>& respDataList = + resp->continue_req_or_response_data(); + + for (unsigned int i = 0 ; i < respDataList.size() ; ++i) + { + if (respDataList[i]->response_data() == NULL) + continue; + + const IMAPParser::capability_data* capaData = + respDataList[i]->response_data()->capability_data(); + + 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(caps[j]->atom()->value()); + } + } + } + + return res; +} + + +ref <security::authenticator> IMAPConnection::getAuthenticator() +{ + return m_auth; } diff --git a/src/net/imap/IMAPFolder.cpp b/src/net/imap/IMAPFolder.cpp index ea0ddfd9..a0df8f6b 100644 --- a/src/net/imap/IMAPFolder.cpp +++ b/src/net/imap/IMAPFolder.cpp @@ -133,7 +133,7 @@ void IMAPFolder::open(const int mode, bool failIfModeIsNotAvailable) // Open a connection for this folder ref <IMAPConnection> connection = - vmime::create <IMAPConnection>(m_store, m_store->oneTimeAuthenticator()); + vmime::create <IMAPConnection>(m_store, m_store->getAuthenticator()); try { diff --git a/src/net/imap/IMAPStore.cpp b/src/net/imap/IMAPStore.cpp index c8dab178..b7a9cbed 100644 --- a/src/net/imap/IMAPStore.cpp +++ b/src/net/imap/IMAPStore.cpp @@ -32,67 +32,23 @@ namespace net { namespace imap { -#ifndef VMIME_BUILDING_DOC - -// -// IMAPauthenticator: private class used internally -// -// Used to request user credentials only in the first authentication -// and reuse this information the next times -// - -class IMAPauthenticator : public authenticator +IMAPStore::IMAPStore(ref <session> sess, ref <security::authenticator> auth) + : store(sess, getInfosInstance(), auth), m_connection(NULL) { -public: +} - IMAPauthenticator(ref <authenticator> auth) - : m_auth(auth), m_infos(NULL) - { - } - ~IMAPauthenticator() +IMAPStore::~IMAPStore() +{ + try { + if (isConnected()) + disconnect(); } - - const authenticationInfos requestAuthInfos() const + catch (vmime::exception&) { - if (m_infos == NULL) - m_infos = vmime::create <authenticationInfos>(m_auth->requestAuthInfos()); - - return (*m_infos); + // Ignore } - -private: - - ref <authenticator> m_auth; - mutable ref <authenticationInfos> m_infos; -}; - -#endif // VMIME_BUILDING_DOC - - - -// -// IMAPStore -// - -IMAPStore::IMAPStore(ref <session> sess, ref <authenticator> auth) - : store(sess, getInfosInstance(), auth), - m_connection(NULL), m_oneTimeAuth(NULL) -{ -} - - -IMAPStore::~IMAPStore() -{ - if (isConnected()) - disconnect(); -} - - -ref <authenticator> IMAPStore::oneTimeAuthenticator() -{ - return (m_oneTimeAuth); } @@ -140,10 +96,8 @@ void IMAPStore::connect() if (isConnected()) throw exceptions::already_connected(); - m_oneTimeAuth = vmime::create <IMAPauthenticator>(getAuthenticator()); - m_connection = vmime::create <IMAPConnection> - (thisWeakRef().dynamicCast <IMAPStore>(), m_oneTimeAuth); + (thisWeakRef().dynamicCast <IMAPStore>(), getAuthenticator()); try { @@ -179,8 +133,6 @@ void IMAPStore::disconnect() m_connection->disconnect(); - m_oneTimeAuth = NULL; - m_connection = NULL; } @@ -263,7 +215,10 @@ const IMAPStore::_infos::props& IMAPStore::_infos::getProperties() const static props p = { // IMAP-specific options - // (none) +#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), @@ -286,7 +241,10 @@ const std::vector <serviceInfos::property> IMAPStore::_infos::getAvailableProper const props& p = getProperties(); // IMAP-specific options - // (none) +#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); diff --git a/src/net/maildir/maildirStore.cpp b/src/net/maildir/maildirStore.cpp index 731024fa..5ddc0897 100644 --- a/src/net/maildir/maildirStore.cpp +++ b/src/net/maildir/maildirStore.cpp @@ -39,7 +39,7 @@ namespace net { namespace maildir { -maildirStore::maildirStore(ref <session> sess, ref <authenticator> auth) +maildirStore::maildirStore(ref <session> sess, ref <security::authenticator> auth) : store(sess, getInfosInstance(), auth), m_connected(false) { } @@ -47,8 +47,15 @@ maildirStore::maildirStore(ref <session> sess, ref <authenticator> auth) maildirStore::~maildirStore() { - if (isConnected()) - disconnect(); + try + { + if (isConnected()) + disconnect(); + } + catch (vmime::exception&) + { + // Ignore + } } @@ -96,7 +103,7 @@ const bool maildirStore::isValidFolderName(const folder::path::component& name) const string& buf = name.getBuffer(); // Name cannot start/end with spaces - if (utility::stringUtils::trim(buf) != name.getBuffer()) + if (utility::stringUtils::trim(buf) != buf) return false; // Name cannot start with '.' 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); diff --git a/src/net/sendmail/sendmailTransport.cpp b/src/net/sendmail/sendmailTransport.cpp index 1b6edff9..b8b9f912 100644 --- a/src/net/sendmail/sendmailTransport.cpp +++ b/src/net/sendmail/sendmailTransport.cpp @@ -46,7 +46,7 @@ namespace net { namespace sendmail { -sendmailTransport::sendmailTransport(ref <session> sess, ref <authenticator> auth) +sendmailTransport::sendmailTransport(ref <session> sess, ref <security::authenticator> auth) : transport(sess, getInfosInstance(), auth), m_connected(false) { } @@ -54,8 +54,15 @@ sendmailTransport::sendmailTransport(ref <session> sess, ref <authenticator> aut sendmailTransport::~sendmailTransport() { - if (isConnected()) - disconnect(); + try + { + if (isConnected()) + disconnect(); + } + catch (vmime::exception&) + { + // Ignore + } } diff --git a/src/net/service.cpp b/src/net/service.cpp index 9cf59d84..018bae48 100644 --- a/src/net/service.cpp +++ b/src/net/service.cpp @@ -17,22 +17,33 @@ // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // +#include "vmime/config.hpp" #include "vmime/net/service.hpp" -#include "vmime/net/defaultAuthenticator.hpp" +#if VMIME_HAVE_SASL_SUPPORT + #include "vmime/security/sasl/defaultSASLAuthenticator.hpp" +#else + #include "vmime/security/defaultAuthenticator.hpp" +#endif // VMIME_HAVE_SASL_SUPPORT namespace vmime { namespace net { -service::service(ref <session> sess, const serviceInfos& infos, ref <authenticator> auth) +service::service(ref <session> sess, const serviceInfos& /* infos */, + ref <security::authenticator> auth) : m_session(sess), m_auth(auth) { if (!auth) { - m_auth = vmime::create <defaultAuthenticator> - (sess, infos.getPropertyPrefix()); +#if VMIME_HAVE_SASL_SUPPORT + m_auth = vmime::create + <security::sasl::defaultSASLAuthenticator>(); +#else + m_auth = vmime::create + <security::defaultAuthenticator>(); +#endif // VMIME_HAVE_SASL_SUPPORT } } @@ -54,17 +65,23 @@ ref <session> service::getSession() } -ref <const authenticator> service::getAuthenticator() const +ref <const security::authenticator> service::getAuthenticator() const { return (m_auth); } -ref <authenticator> service::getAuthenticator() +ref <security::authenticator> service::getAuthenticator() { return (m_auth); } +void service::setAuthenticator(ref <security::authenticator> auth) +{ + m_auth = auth; +} + + } // net } // vmime diff --git a/src/net/serviceFactory.cpp b/src/net/serviceFactory.cpp index 43697cc8..f4b39925 100644 --- a/src/net/serviceFactory.cpp +++ b/src/net/serviceFactory.cpp @@ -48,14 +48,16 @@ serviceFactory* serviceFactory::getInstance() ref <service> serviceFactory::create - (ref <session> sess, const string& protocol, ref <authenticator> auth) + (ref <session> sess, const string& protocol, + ref <security::authenticator> auth) { return (getServiceByProtocol(protocol)->create(sess, auth)); } ref <service> serviceFactory::create - (ref <session> sess, const utility::url& u, ref <authenticator> auth) + (ref <session> sess, const utility::url& u, + ref <security::authenticator> auth) { ref <service> serv = create(sess, u.getProtocol(), auth); diff --git a/src/net/session.cpp b/src/net/session.cpp index 2c46bf60..f6b0fdd1 100644 --- a/src/net/session.cpp +++ b/src/net/session.cpp @@ -50,13 +50,14 @@ session::~session() } -ref <transport> session::getTransport(ref <authenticator> auth) +ref <transport> session::getTransport(ref <security::authenticator> auth) { return (getTransport(m_props["transport.protocol"], auth)); } -ref <transport> session::getTransport(const string& protocol, ref <authenticator> auth) +ref <transport> session::getTransport + (const string& protocol, ref <security::authenticator> auth) { ref <session> sess = thisRef().dynamicCast <session>(); ref <service> sv = serviceFactory::getInstance()->create(sess, protocol, auth); @@ -68,7 +69,8 @@ ref <transport> session::getTransport(const string& protocol, ref <authenticator } -ref <transport> session::getTransport(const utility::url& url, ref <authenticator> auth) +ref <transport> session::getTransport + (const utility::url& url, ref <security::authenticator> auth) { ref <session> sess = thisRef().dynamicCast <session>(); ref <service> sv = serviceFactory::getInstance()->create(sess, url, auth); @@ -80,13 +82,14 @@ ref <transport> session::getTransport(const utility::url& url, ref <authenticato } -ref <store> session::getStore(ref <authenticator> auth) +ref <store> session::getStore(ref <security::authenticator> auth) { return (getStore(m_props["store.protocol"], auth)); } -ref <store> session::getStore(const string& protocol, ref <authenticator> auth) +ref <store> session::getStore + (const string& protocol, ref <security::authenticator> auth) { ref <session> sess = thisRef().dynamicCast <session>(); ref <service> sv = serviceFactory::getInstance()->create(sess, protocol, auth); @@ -98,7 +101,8 @@ ref <store> session::getStore(const string& protocol, ref <authenticator> auth) } -ref <store> session::getStore(const utility::url& url, ref <authenticator> auth) +ref <store> session::getStore + (const utility::url& url, ref <security::authenticator> auth) { ref <session> sess = thisRef().dynamicCast <session>(); ref <service> sv = serviceFactory::getInstance()->create(sess, url, auth); diff --git a/src/net/simpleAuthenticator.cpp b/src/net/simpleAuthenticator.cpp deleted file mode 100644 index af125f77..00000000 --- a/src/net/simpleAuthenticator.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// -// VMime library (http://www.vmime.org) -// Copyright (C) 2002-2005 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. -// - -#include "vmime/net/simpleAuthenticator.hpp" - - -namespace vmime { -namespace net { - - -simpleAuthenticator::simpleAuthenticator() -{ -} - - -simpleAuthenticator::simpleAuthenticator(const string& username, const string& password) - : m_username(username), m_password(password) -{ -} - - -const authenticationInfos simpleAuthenticator::requestAuthInfos() const -{ - return (authenticationInfos(m_username, m_password)); -} - - -const string& simpleAuthenticator::getUsername() const -{ - return (m_username); -} - - -void simpleAuthenticator::setUsername(const string& username) -{ - m_username = username; -} - - -const string& simpleAuthenticator::getPassword() const -{ - return (m_password); -} - - -void simpleAuthenticator::setPassword(const string& password) -{ - m_password = password; -} - - -} // net -} // vmime diff --git a/src/net/smtp/SMTPTransport.cpp b/src/net/smtp/SMTPTransport.cpp index 1cbffcf5..a5f034a6 100644 --- a/src/net/smtp/SMTPTransport.cpp +++ b/src/net/smtp/SMTPTransport.cpp @@ -27,6 +27,11 @@ #include "vmime/net/authHelper.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 // Helpers for service properties @@ -41,7 +46,7 @@ namespace net { namespace smtp { -SMTPTransport::SMTPTransport(ref <session> sess, ref <authenticator> auth) +SMTPTransport::SMTPTransport(ref <session> sess, ref <security::authenticator> auth) : transport(sess, getInfosInstance(), auth), m_socket(NULL), m_authentified(false), m_extendedSMTP(false), m_timeoutHandler(NULL) { @@ -50,10 +55,17 @@ SMTPTransport::SMTPTransport(ref <session> sess, ref <authenticator> auth) SMTPTransport::~SMTPTransport() { - if (isConnected()) - disconnect(); - else if (m_socket) - internalDisconnect(); + try + { + if (isConnected()) + disconnect(); + else if (m_socket) + internalDisconnect(); + } + catch (vmime::exception&) + { + // Ignore + } } @@ -87,15 +99,16 @@ void SMTPTransport::connect() m_socket = sf->create(); m_socket->connect(address, port); + m_responseBuffer.clear(); + // Connection // // eg: C: <connection to server> // --- S: 220 smtp.domain.com Service ready string response; - readResponse(response); - if (responseCode(response) != 220) + if (readAllResponses(response) != 220) { internalDisconnect(); throw exceptions::connection_greeting_error(response); @@ -105,12 +118,12 @@ void SMTPTransport::connect() // First, try Extended SMTP (ESMTP) // // eg: C: EHLO thismachine.ourdomain.com - // S: 250 OK + // S: 250-smtp.theserver.com + // S: 250 AUTH CRAM-MD5 DIGEST-MD5 sendRequest("EHLO " + platformDependant::getHandler()->getHostName()); - readResponse(response); - if (responseCode(response) != 250) + if (readAllResponses(response, true) != 250) { // Next, try "Basic" SMTP // @@ -118,9 +131,8 @@ void SMTPTransport::connect() // S: 250 OK sendRequest("HELO " + platformDependant::getHandler()->getHostName()); - readResponse(response); - if (responseCode(response) != 250) + if (readAllResponses(response) != 250) { internalDisconnect(); throw exceptions::connection_greeting_error(response); @@ -131,93 +143,231 @@ void SMTPTransport::connect() else { m_extendedSMTP = true; + m_extendedSMTPResponse = response; } // Authentication if (GET_PROPERTY(bool, PROPERTY_OPTIONS_NEEDAUTH)) + authenticate(); +} + + +void SMTPTransport::authenticate() +{ + if (!m_extendedSMTP) { - if (!m_extendedSMTP) + internalDisconnect(); + throw exceptions::command_error("AUTH", "ESMTP not supported."); + } + + getAuthenticator()->setService(thisRef().dynamicCast <service>()); + +#if VMIME_HAVE_SASL_SUPPORT + // First, try SASL authentication + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL)) + { + try + { + authenticateSASL(); + + m_authentified = true; + 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 exceptions::command_error("AUTH", "ESMTP not supported."); + throw e; } + } +#endif // VMIME_HAVE_SASL_SUPPORT - const authenticationInfos auth = getAuthenticator()->requestAuthInfos(); - bool authentified = false; + // No other authentication method is possible + throw exceptions::authentication_error("All authentication methods failed"); +} - enum AuthMethods - { - First = 0, - CRAM_MD5 = First, - // TODO: more authentication methods... - End - }; - for (int currentMethod = First ; !authentified ; ++currentMethod) +#if VMIME_HAVE_SASL_SUPPORT + +void SMTPTransport::authenticateSASL() +{ + if (!getAuthenticator().dynamicCast <security::sasl::SASLAuthenticator>()) + throw exceptions::authentication_error("No SASL authenticator available."); + + // Obtain SASL mechanisms supported by server from EHLO response + std::vector <string> saslMechs; + std::istringstream iss(m_extendedSMTPResponse); + + while (!iss.eof()) + { + string line; + std::getline(iss, line); + + std::istringstream liss(line); + string word; + + bool inAuth = false; + + while (liss >> word) { - switch (currentMethod) + if (word.length() == 4 && + (word[0] == 'A' || word[0] == 'a') || + (word[0] == 'U' || word[0] == 'u') || + (word[0] == 'T' || word[0] == 't') || + (word[0] == 'H' || word[0] == 'h')) { - case CRAM_MD5: + inAuth = true; + } + else if (inAuth) { - sendRequest("AUTH CRAM-MD5"); - readResponse(response); + saslMechs.push_back(word); + } + } + } - if (responseCode(response) == 334) - { - encoderB64 base64; + if (saslMechs.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); - string challengeB64 = responseText(response); - string challenge, challengeHex; + std::vector <ref <security::sasl::SASLMechanism> > mechList; - { - utility::inputStreamStringAdapter in(challengeB64); - utility::outputStreamStringAdapter out(challenge); + ref <security::sasl::SASLContext> saslContext = + vmime::create <security::sasl::SASLContext>(); - base64.decode(in, out); - } + for (unsigned int i = 0 ; i < saslMechs.size() ; ++i) + { + try + { + mechList.push_back + (saslContext->createMechanism(saslMechs[i])); + } + catch (exceptions::no_such_mechanism&) + { + // Ignore mechanism + } + } - hmac_md5(challenge, auth.getPassword(), challengeHex); + if (mechList.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); - string decoded = auth.getUsername() + " " + challengeHex; - string encoded; + // Try to suggest a mechanism among all those supported + ref <security::sasl::SASLMechanism> suggestedMech = + saslContext->suggestMechanism(mechList); - { - utility::inputStreamStringAdapter in(decoded); - utility::outputStreamStringAdapter out(encoded); + if (!suggestedMech) + throw exceptions::authentication_error("Unable to suggest SASL mechanism."); - base64.encode(in, out); - } + // Allow application to choose which mechanisms to use + mechList = getAuthenticator().dynamicCast <security::sasl::SASLAuthenticator>()-> + getAcceptableMechanisms(mechList, suggestedMech); - sendRequest(encoded); - readResponse(response); + if (mechList.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); - if (responseCode(response) == 235) + // 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("smtp", getAuthenticator(), mech); + + saslSession->init(); + + sendRequest("AUTH " + mech->getName()); + + for (bool cont = true ; cont ; ) + { + string response; + + switch (readAllResponses(response)) + { + case 235: + { + m_socket = saslSession->getSecuredSocket(m_socket); + return; + } + case 334: + { + byte* challenge = 0; + int challengeLen = 0; + + byte* resp = 0; + int respLen = 0; + + try + { + // Extract challenge + 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) { - authentified = true; + delete [] challenge; + challenge = NULL; } - else + + if (resp) { - internalDisconnect(); - throw exceptions::authentication_error(response); + 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; } - case End: - { - // All authentication methods have been tried and - // the server does not understand any. - throw exceptions::authentication_error(response); - } + default: + cont = false; + break; } } } - m_authentified = true; + throw exceptions::authentication_error + ("Could not authenticate using SASL: all mechanisms failed."); } +#endif // VMIME_HAVE_SASL_SUPPORT + const bool SMTPTransport::isConnected() const { @@ -250,12 +400,11 @@ void SMTPTransport::internalDisconnect() void SMTPTransport::noop() { - m_socket->send("NOOP"); + sendRequest("NOOP"); string response; - readResponse(response); - if (responseCode(response) != 250) + if (readAllResponses(response) != 250) throw exceptions::command_error("NOOP", response); } @@ -274,9 +423,8 @@ void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients string response; sendRequest("MAIL FROM: <" + expeditor.getEmail() + ">"); - readResponse(response); - if (responseCode(response) != 250) + if (readAllResponses(response) != 250) { internalDisconnect(); throw exceptions::command_error("MAIL", response); @@ -288,9 +436,8 @@ void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients const mailbox& mbox = *recipients.getMailboxAt(i); sendRequest("RCPT TO: <" + mbox.getEmail() + ">"); - readResponse(response); - if (responseCode(response) != 250) + if (readAllResponses(response) != 250) { internalDisconnect(); throw exceptions::command_error("RCPT TO", response); @@ -299,9 +446,8 @@ void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients // Send the message data sendRequest("DATA"); - readResponse(response); - if (responseCode(response) != 354) + if (readAllResponses(response) != 354) { internalDisconnect(); throw exceptions::command_error("DATA", response); @@ -315,9 +461,8 @@ void SMTPTransport::send(const mailbox& expeditor, const mailboxList& recipients // Send end-of-data delimiter m_socket->sendRaw("\r\n.\r\n", 5); - readResponse(response); - if (responseCode(response) != 250) + if (readAllResponses(response) != 250) { internalDisconnect(); throw exceptions::command_error("DATA", response); @@ -332,7 +477,7 @@ void SMTPTransport::sendRequest(const string& buffer, const bool end) } -const int SMTPTransport::responseCode(const string& response) +const int SMTPTransport::getResponseCode(const string& response) { int code = 0; @@ -347,35 +492,25 @@ const int SMTPTransport::responseCode(const string& response) } -const string SMTPTransport::responseText(const string& response) +const string SMTPTransport::readResponseLine() { - string text; + string currentBuffer = m_responseBuffer; - std::istringstream iss(response); - std::string line; - - while (std::getline(iss, line)) + while (true) { - if (line.length() >= 4) - text += line.substr(4); - else - text += line; - - text += "\n"; - } - - return (text); -} + // Get a line from the response buffer + string::size_type lineEnd = currentBuffer.find_first_of('\n'); + if (lineEnd != string::npos) + { + const string line(currentBuffer.begin(), currentBuffer.begin() + lineEnd); -void SMTPTransport::readResponse(string& buffer) -{ - bool foundTerminator = false; + currentBuffer.erase(currentBuffer.begin(), currentBuffer.begin() + lineEnd + 1); + m_responseBuffer = currentBuffer; - buffer.clear(); + return line; + } - for ( ; !foundTerminator ; ) - { // Check whether the time-out delay is elapsed if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) { @@ -393,40 +528,61 @@ void SMTPTransport::readResponse(string& buffer) continue; } - // We have received data: reset the time-out counter - if (m_timeoutHandler) - m_timeoutHandler->resetTimeOut(); + currentBuffer += receiveBuffer; + } +} - // Append the data to the response buffer - buffer += receiveBuffer; - // Check for terminator string (and strip it if present) - if (buffer.length() >= 2 && buffer[buffer.length() - 1] == '\n') - { - string::size_type p = buffer.length() - 2; - bool end = false; +const int SMTPTransport::readResponse(string& text) +{ + string line = readResponseLine(); - for ( ; !end ; --p) - { - if (p == 0 || buffer[p] == '\n') - { - end = true; + // Special case where CRLF occurs after response code + if (line.length() < 4) + line = line + '\n' + readResponseLine(); - if (p + 4 < buffer.length()) - foundTerminator = true; - } - } - } - } + const int code = getResponseCode(line); + + m_responseContinues = (line.length() >= 4 && line[3] == '-'); + + if (line.length() > 4) + text = utility::stringUtils::trim(line.substr(4)); + else + text = utility::stringUtils::trim(line); - // Remove [CR]LF at the end of the response - if (buffer.length() >= 2 && buffer[buffer.length() - 1] == '\n') + return code; +} + + +const int SMTPTransport::readAllResponses(string& outText, const bool allText) +{ + string text; + + const int firstCode = readResponse(outText); + + if (allText) + text = outText; + + while (m_responseContinues) { - if (buffer[buffer.length() - 2] == '\r') - buffer.resize(buffer.length() - 2); - else - buffer.resize(buffer.length() - 1); + const int code = readResponse(outText); + + if (allText) + text += '\n' + outText; + + if (code != firstCode) + { + if (allText) + outText = text; + + return 0; + } } + + if (allText) + outText = text; + + return firstCode; } @@ -460,6 +616,10 @@ const SMTPTransport::_infos::props& SMTPTransport::_infos::getProperties() const { // SMTP-specific options property("options.need-authentication", serviceInfos::property::TYPE_BOOL, "false"), +#if VMIME_HAVE_SASL_SUPPORT + property("options.sasl", serviceInfos::property::TYPE_BOOL, "true"), + property("options.sasl.fallback", serviceInfos::property::TYPE_BOOL, "false"), +#endif // VMIME_HAVE_SASL_SUPPORT // Common properties property(serviceInfos::property::AUTH_USERNAME), @@ -483,6 +643,10 @@ const std::vector <serviceInfos::property> SMTPTransport::_infos::getAvailablePr // SMTP-specific options list.push_back(p.PROPERTY_OPTIONS_NEEDAUTH); +#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); diff --git a/src/net/transport.cpp b/src/net/transport.cpp index 4f9acbeb..be0837cb 100644 --- a/src/net/transport.cpp +++ b/src/net/transport.cpp @@ -28,7 +28,7 @@ namespace vmime { namespace net { -transport::transport(ref <session> sess, const serviceInfos& infos, ref <authenticator> auth) +transport::transport(ref <session> sess, const serviceInfos& infos, ref <security::authenticator> auth) : service(sess, infos, auth) { } |