diff options
Diffstat (limited to '')
24 files changed, 2204 insertions, 494 deletions
diff --git a/src/exception.cpp b/src/exception.cpp index 7482482a..4fbd4060 100644 --- a/src/exception.cpp +++ b/src/exception.cpp @@ -347,8 +347,7 @@ const char* net_exception::name() const throw() { return "net_exception"; } socket_exception::~socket_exception() throw() {} socket_exception::socket_exception(const string& what, const exception& other) : net_exception(what.empty() - ? "Socket error." - : "Socket error: '" + what + "'.", other) {} + ? "Socket error." : what, other) {} exception* socket_exception::clone() const { return new socket_exception(*this); } const char* socket_exception::name() const throw() { return "socket_exception"; } @@ -361,8 +360,7 @@ const char* socket_exception::name() const throw() { return "socket_exception"; connection_error::~connection_error() throw() {} connection_error::connection_error(const string& what, const exception& other) : socket_exception(what.empty() - ? "Connection error." - : "Connection error: '" + what + "'.", other) {} + ? "Connection error." : what, other) {} exception* connection_error::clone() const { return new connection_error(*this); } const char* connection_error::name() const throw() { return "connection_error"; } @@ -675,6 +673,48 @@ const char* file_not_found::name() const throw() { return "file_not_found"; } #endif // VMIME_HAVE_FILESYSTEM_FEATURES +#if VMIME_HAVE_SASL_SUPPORT + + +// +// sasl_exception +// + +sasl_exception::~sasl_exception() throw() {} +sasl_exception::sasl_exception(const string& what, const exception& other) + : exception(what, other) {} + +exception* sasl_exception::clone() const { return new sasl_exception(*this); } +const char* sasl_exception::name() const throw() { return "sasl_exception"; } + + +// +// no_such_mechanism +// + +no_such_mechanism::~no_such_mechanism() throw() {} +no_such_mechanism::no_such_mechanism(const string& name, const exception& other) + : sasl_exception("No such SASL mechanism: '" + name + "'.", other) {} + +exception* no_such_mechanism::clone() const { return new no_such_mechanism(*this); } +const char* no_such_mechanism::name() const throw() { return "no_such_mechanism"; } + + +// +// no_auth_information +// + +no_auth_information::~no_auth_information() throw() {} +no_auth_information::no_auth_information(const exception& other) + : sasl_exception("Information cannot be provided.", other) {} + +exception* no_auth_information::clone() const { return new no_auth_information(*this); } +const char* no_auth_information::name() const throw() { return "no_auth_information"; } + + +#endif // VMIME_HAVE_SASL_SUPPORT + + } // exceptions 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) { } diff --git a/src/security/defaultAuthenticator.cpp b/src/security/defaultAuthenticator.cpp new file mode 100644 index 00000000..913eb07a --- /dev/null +++ b/src/security/defaultAuthenticator.cpp @@ -0,0 +1,98 @@ +// +// 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/security/defaultAuthenticator.hpp" + +#include "vmime/net/service.hpp" + +#include "vmime/platformDependant.hpp" + + +namespace vmime { +namespace security { + + +defaultAuthenticator::defaultAuthenticator() +{ +} + + +defaultAuthenticator::~defaultAuthenticator() +{ +} + + +const string defaultAuthenticator::getUsername() const +{ + const string& prefix = m_service->getInfos().getPropertyPrefix(); + const propertySet& props = m_service->getSession()->getProperties(); + + if (props.hasProperty(prefix + net::serviceInfos::property::AUTH_USERNAME.getName())) + return props[prefix + net::serviceInfos::property::AUTH_USERNAME.getName()]; + + throw exceptions::no_auth_information(); +} + + +const string defaultAuthenticator::getPassword() const +{ + const string& prefix = m_service->getInfos().getPropertyPrefix(); + const propertySet& props = m_service->getSession()->getProperties(); + + if (props.hasProperty(prefix + net::serviceInfos::property::AUTH_PASSWORD.getName())) + return props[prefix + net::serviceInfos::property::AUTH_PASSWORD.getName()]; + + throw exceptions::no_auth_information(); +} + + +const string defaultAuthenticator::getHostname() const +{ + return platformDependant::getHandler()->getHostName(); +} + + +const string defaultAuthenticator::getAnonymousToken() const +{ + return "anonymous@" + platformDependant::getHandler()->getHostName(); +} + + +const string defaultAuthenticator::getServiceName() const +{ + // Information cannot be provided + throw exceptions::no_auth_information(); +} + + +void defaultAuthenticator::setService(ref <net::service> serv) +{ + m_service = serv; +} + + +weak_ref <net::service> defaultAuthenticator::getService() const +{ + return m_service; +} + + +} // security +} // vmime + diff --git a/src/security/sasl/SASLContext.cpp b/src/security/sasl/SASLContext.cpp new file mode 100644 index 00000000..dd8cbd93 --- /dev/null +++ b/src/security/sasl/SASLContext.cpp @@ -0,0 +1,189 @@ +// +// 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 <sstream> + +#include <gsasl.h> + +#include "vmime/security/sasl/SASLContext.hpp" +#include "vmime/security/sasl/SASLMechanism.hpp" + +#include "vmime/base.hpp" +#include "vmime/encoderFactory.hpp" + +#include "vmime/utility/stream.hpp" + + +namespace vmime { +namespace security { +namespace sasl { + + +SASLContext::SASLContext() +{ + if (gsasl_init(&m_gsaslContext) != GSASL_OK) + throw std::bad_alloc(); +} + + +SASLContext::~SASLContext() +{ + gsasl_done(m_gsaslContext); +} + + +ref <SASLSession> SASLContext::createSession + (const string& serviceName, + ref <authenticator> auth, ref <SASLMechanism> mech) +{ + return vmime::create <SASLSession> + (serviceName, thisRef().dynamicCast <SASLContext>(), auth, mech); +} + + +ref <SASLMechanism> SASLContext::createMechanism(const string& name) +{ + return SASLMechanismFactory::getInstance()->create + (thisRef().dynamicCast <SASLContext>(), name); +} + + +ref <SASLMechanism> SASLContext::suggestMechanism + (const std::vector <ref <SASLMechanism> >& mechs) +{ + if (mechs.empty()) + return 0; + + std::ostringstream oss; + + for (unsigned int i = 0 ; i < mechs.size() ; ++i) + oss << mechs[i]->getName() << " "; + + const string mechList = oss.str(); + const char* suggested = gsasl_client_suggest_mechanism + (m_gsaslContext, mechList.c_str()); + + if (suggested) + { + for (unsigned int i = 0 ; i < mechs.size() ; ++i) + { + if (mechs[i]->getName() == suggested) + return mechs[i]; + } + } + + return 0; +} + + +void SASLContext::decodeB64(const string& input, byte** output, int* outputLen) +{ + string res; + + utility::inputStreamStringAdapter is(input); + utility::outputStreamStringAdapter os(res); + + ref <encoder> dec = encoderFactory::getInstance()->create("base64"); + + dec->decode(is, os); + + byte* out = new byte[res.length()]; + + std::copy(res.begin(), res.end(), out); + + *output = out; + *outputLen = res.length(); +} + + +const string SASLContext::encodeB64(const byte* input, const int inputLen) +{ + string res; + + utility::inputStreamByteBufferAdapter is(input, inputLen); + utility::outputStreamStringAdapter os(res); + + ref <encoder> enc = encoderFactory::getInstance()->create("base64"); + + enc->encode(is, os); + + return res; +} + + +const string SASLContext::getErrorMessage(const string& fname, const int code) +{ + string msg = fname + "() returned "; + +#define ERROR(x) \ + case x: msg += #x; break; + + switch (code) + { + ERROR(GSASL_NEEDS_MORE) + ERROR(GSASL_UNKNOWN_MECHANISM) + ERROR(GSASL_MECHANISM_CALLED_TOO_MANY_TIMES) + ERROR(GSASL_MALLOC_ERROR) + ERROR(GSASL_BASE64_ERROR) + ERROR(GSASL_CRYPTO_ERROR) + ERROR(GSASL_SASLPREP_ERROR) + ERROR(GSASL_MECHANISM_PARSE_ERROR) + ERROR(GSASL_AUTHENTICATION_ERROR) + ERROR(GSASL_INTEGRITY_ERROR) + ERROR(GSASL_NO_CLIENT_CODE) + ERROR(GSASL_NO_SERVER_CODE) + ERROR(GSASL_NO_CALLBACK) + ERROR(GSASL_NO_ANONYMOUS_TOKEN) + ERROR(GSASL_NO_AUTHID) + ERROR(GSASL_NO_AUTHZID) + ERROR(GSASL_NO_PASSWORD) + ERROR(GSASL_NO_PASSCODE) + ERROR(GSASL_NO_PIN) + ERROR(GSASL_NO_SERVICE) + ERROR(GSASL_NO_HOSTNAME) + ERROR(GSASL_GSSAPI_RELEASE_BUFFER_ERROR) + ERROR(GSASL_GSSAPI_IMPORT_NAME_ERROR) + ERROR(GSASL_GSSAPI_INIT_SEC_CONTEXT_ERROR) + ERROR(GSASL_GSSAPI_ACCEPT_SEC_CONTEXT_ERROR) + ERROR(GSASL_GSSAPI_UNWRAP_ERROR) + ERROR(GSASL_GSSAPI_WRAP_ERROR) + ERROR(GSASL_GSSAPI_ACQUIRE_CRED_ERROR) + ERROR(GSASL_GSSAPI_DISPLAY_NAME_ERROR) + ERROR(GSASL_GSSAPI_UNSUPPORTED_PROTECTION_ERROR) + ERROR(GSASL_KERBEROS_V5_INIT_ERROR) + ERROR(GSASL_KERBEROS_V5_INTERNAL_ERROR) + ERROR(GSASL_SECURID_SERVER_NEED_ADDITIONAL_PASSCODE) + ERROR(GSASL_SECURID_SERVER_NEED_NEW_PIN) + + default: + + msg += "unknown error"; + break; + } + +#undef ERROR + + return msg; +} + + +} // sasl +} // security +} // vmime + diff --git a/src/security/sasl/SASLMechanismFactory.cpp b/src/security/sasl/SASLMechanismFactory.cpp new file mode 100644 index 00000000..fda4448c --- /dev/null +++ b/src/security/sasl/SASLMechanismFactory.cpp @@ -0,0 +1,131 @@ +// +// 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 <stdexcept> +#include <new> + +#include <gsasl.h> + +#include "vmime/security/sasl/SASLMechanismFactory.hpp" +#include "vmime/security/sasl/builtinSASLMechanism.hpp" +#include "vmime/security/sasl/SASLContext.hpp" + +#include "vmime/utility/stringUtils.hpp" + +#include "vmime/base.hpp" +#include "vmime/exception.hpp" + + +namespace vmime { +namespace security { +namespace sasl { + + +SASLMechanismFactory::SASLMechanismFactory() +{ + if (gsasl_init(&m_gsaslContext) != GSASL_OK) + throw std::bad_alloc(); +} + + +SASLMechanismFactory::~SASLMechanismFactory() +{ + gsasl_done(m_gsaslContext); +} + + +// static +SASLMechanismFactory* SASLMechanismFactory::getInstance() +{ + static SASLMechanismFactory instance; + return &instance; +} + + +ref <SASLMechanism> SASLMechanismFactory::create + (ref <SASLContext> ctx, const string& name_) +{ + const string name(utility::stringUtils::toUpper(name_)); + + // Check for built-in mechanisms + if (isMechanismSupported(name)) + { + return vmime::create <builtinSASLMechanism>(ctx, name); + } + // Check for registered mechanisms + else + { + MapType::iterator it = m_mechs.find(name); + + if (it != m_mechs.end()) + return (*it).second->create(ctx, name); + } + + throw exceptions::no_such_mechanism(name); + return 0; +} + + +const std::vector <string> SASLMechanismFactory::getSupportedMechanisms() const +{ + std::vector <string> list; + + // Registered mechanisms + for (MapType::const_iterator it = m_mechs.begin() ; + it != m_mechs.end() ; ++it) + { + list.push_back((*it).first); + } + + // Built-in mechanisms + char* out = 0; + + if (gsasl_client_mechlist(m_gsaslContext, &out) == GSASL_OK) + { + // 'out' contains SASL mechanism names, separated by spaces + for (char *start = out, *p = out ; ; ++p) + { + if (*p == ' ' || !*p) + { + list.push_back(string(start, p)); + start = p + 1; + + // End of string + if (!*p) break; + } + } + + free(out); + } + + return list; +} + + +const bool SASLMechanismFactory::isMechanismSupported(const string& name) const +{ + return (gsasl_client_support_p(m_gsaslContext, name.c_str()) != 0 || + m_mechs.find(name) != m_mechs.end()); +} + + +} // sasl +} // security +} // vmime + diff --git a/src/security/sasl/SASLSession.cpp b/src/security/sasl/SASLSession.cpp new file mode 100644 index 00000000..f4d8bf13 --- /dev/null +++ b/src/security/sasl/SASLSession.cpp @@ -0,0 +1,179 @@ +// +// 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 <sstream> + +#include <gsasl.h> + +#include "vmime/security/sasl/SASLSession.hpp" + +#include "vmime/security/sasl/SASLContext.hpp" +#include "vmime/security/sasl/SASLSocket.hpp" +#include "vmime/security/sasl/SASLAuthenticator.hpp" + + +namespace vmime { +namespace security { +namespace sasl { + + +SASLSession::SASLSession(const string& serviceName, ref <SASLContext> ctx, + ref <authenticator> auth, ref <SASLMechanism> mech) + : m_serviceName(serviceName), m_context(ctx), m_auth(auth), + m_mech(mech), m_gsaslContext(0), m_gsaslSession(0) +{ + if (gsasl_init(&m_gsaslContext) != GSASL_OK) + throw std::bad_alloc(); + + gsasl_client_start(m_gsaslContext, mech->getName().c_str(), &m_gsaslSession); + + gsasl_callback_set(m_gsaslContext, gsaslCallback); + gsasl_callback_hook_set(m_gsaslContext, this); +} + + +SASLSession::~SASLSession() +{ + gsasl_finish(m_gsaslSession); + m_gsaslSession = 0; + + gsasl_done(m_gsaslContext); + m_gsaslContext = 0; +} + + +void SASLSession::init() +{ + ref <SASLAuthenticator> saslAuth = m_auth.dynamicCast <SASLAuthenticator>(); + + if (saslAuth) + { + saslAuth->setSASLMechanism(m_mech); + saslAuth->setSASLSession(thisRef().dynamicCast <SASLSession>()); + } +} + + +ref <authenticator> SASLSession::getAuthenticator() +{ + return m_auth; +} + + +ref <SASLMechanism> SASLSession::getMechanism() +{ + return m_mech; +} + + +ref <SASLContext> SASLSession::getContext() +{ + return m_context; +} + + +const bool SASLSession::evaluateChallenge + (const byte* challenge, const int challengeLen, + byte** response, int* responseLen) +{ + return m_mech->step(thisRef().dynamicCast <SASLSession>(), + challenge, challengeLen, response, responseLen); +} + + +ref <net::socket> SASLSession::getSecuredSocket(ref <net::socket> sok) +{ + return vmime::create <SASLSocket>(thisRef().dynamicCast <SASLSession>(), sok); +} + + +const string SASLSession::getServiceName() const +{ + return m_serviceName; +} + + +// static +int SASLSession::gsaslCallback + (Gsasl* ctx, Gsasl_session* sctx, Gsasl_property prop) +{ + SASLSession* sess = reinterpret_cast <SASLSession*>(gsasl_callback_hook_get(ctx)); + if (!sess) return GSASL_AUTHENTICATION_ERROR; + + ref <authenticator> auth = sess->getAuthenticator(); + + try + { + string res; + + switch (prop) + { + case GSASL_AUTHID: + + res = auth->getUsername(); + break; + + case GSASL_PASSWORD: + + res = auth->getPassword(); + break; + + case GSASL_ANONYMOUS_TOKEN: + + res = auth->getAnonymousToken(); + break; + + case GSASL_HOSTNAME: + + res = auth->getHostname(); + break; + + case GSASL_SERVICE: + + res = auth->getServiceName(); + break; + + case GSASL_AUTHZID: + case GSASL_GSSAPI_DISPLAY_NAME: + case GSASL_PASSCODE: + case GSASL_SUGGESTED_PIN: + case GSASL_PIN: + case GSASL_REALM: + + default: + + return GSASL_NO_CALLBACK; + } + + gsasl_property_set(sctx, prop, res.c_str()); + + return GSASL_OK; + } + //catch (exceptions::no_auth_information&) + catch (...) + { + return GSASL_NO_CALLBACK; + } +} + + +} // sasl +} // security +} // vmime + diff --git a/src/security/sasl/SASLSocket.cpp b/src/security/sasl/SASLSocket.cpp new file mode 100644 index 00000000..321f90f7 --- /dev/null +++ b/src/security/sasl/SASLSocket.cpp @@ -0,0 +1,167 @@ +// +// 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/security/sasl/SASLSocket.hpp" +#include "vmime/security/sasl/SASLSession.hpp" + +#include "vmime/exception.hpp" + +#include <algorithm> + +#include <gsasl.h> + + +namespace vmime { +namespace security { +namespace sasl { + + + +SASLSocket::SASLSocket(ref <SASLSession> sess, ref <net::socket> wrapped) + : m_session(sess), m_wrapped(wrapped), + m_pendingBuffer(0), m_pendingPos(0), m_pendingLen(0) +{ +} + + +SASLSocket::~SASLSocket() +{ + if (m_pendingBuffer) + delete [] m_pendingBuffer; +} + + +void SASLSocket::connect(const string& address, const port_t port) +{ + m_wrapped->connect(address, port); +} + + +void SASLSocket::disconnect() +{ + m_wrapped->disconnect(); +} + + +const bool SASLSocket::isConnected() const +{ + return m_wrapped->isConnected(); +} + + +void SASLSocket::receive(string& buffer) +{ + const int n = receiveRaw(m_recvBuffer, sizeof(m_recvBuffer)); + + buffer = string(m_recvBuffer, n); +} + + +const int SASLSocket::receiveRaw(char* buffer, const int count) +{ + if (m_pendingLen != 0) + { + const int copyLen = + (count >= m_pendingLen ? m_pendingLen : count); + + std::copy(m_pendingBuffer + m_pendingPos, + m_pendingBuffer + m_pendingPos + copyLen, + buffer); + + m_pendingLen -= copyLen; + m_pendingPos += copyLen; + + if (m_pendingLen == 0) + { + delete [] m_pendingBuffer; + + m_pendingBuffer = 0; + m_pendingPos = 0; + m_pendingLen = 0; + } + + return copyLen; + } + + const int n = m_wrapped->receiveRaw(buffer, count); + + byte* output = 0; + int outputLen = 0; + + m_session->getMechanism()->decode + (m_session, reinterpret_cast <const byte*>(buffer), n, + &output, &outputLen); + + // If we can not copy all decoded data into the output buffer, put + // remaining data into a pending buffer for next calls to receive() + if (outputLen > count) + { + std::copy(output, output + count, buffer); + + m_pendingBuffer = output; + m_pendingLen = outputLen; + m_pendingPos = count; + + return count; + } + else + { + std::copy(output, output + outputLen, buffer); + + delete [] output; + + return outputLen; + } +} + + +void SASLSocket::send(const string& buffer) +{ + sendRaw(buffer.data(), buffer.length()); +} + + +void SASLSocket::sendRaw(const char* buffer, const int count) +{ + byte* output = 0; + int outputLen = 0; + + m_session->getMechanism()->encode + (m_session, reinterpret_cast <const byte*>(buffer), count, + &output, &outputLen); + + try + { + m_wrapped->sendRaw + (reinterpret_cast <const char*>(output), outputLen); + } + catch (...) + { + delete [] output; + throw; + } + + delete [] output; +} + + +} // sasl +} // security +} // vmime + diff --git a/src/security/sasl/builtinSASLMechanism.cpp b/src/security/sasl/builtinSASLMechanism.cpp new file mode 100644 index 00000000..89704eeb --- /dev/null +++ b/src/security/sasl/builtinSASLMechanism.cpp @@ -0,0 +1,182 @@ +// +// 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 <gsasl.h> + +#include "vmime/security/sasl/builtinSASLMechanism.hpp" + +#include "vmime/security/sasl/SASLContext.hpp" +#include "vmime/security/sasl/SASLSession.hpp" + +#include "vmime/exception.hpp" + +#include <stdexcept> +#include <new> + + +namespace vmime { +namespace security { +namespace sasl { + + +builtinSASLMechanism::builtinSASLMechanism(ref <SASLContext> ctx, const string& name) + : m_context(ctx), m_name(name), m_complete(false) +{ +} + + +builtinSASLMechanism::~builtinSASLMechanism() +{ +} + + +const string builtinSASLMechanism::getName() const +{ + return m_name; +} + + +const bool builtinSASLMechanism::step + (ref <SASLSession> sess, const byte* challenge, const int challengeLen, + byte** response, int* responseLen) +{ + char* output = 0; + size_t outputLen = 0; + + const int result = gsasl_step(sess->m_gsaslSession, + reinterpret_cast <const char*>(challenge), challengeLen, + &output, &outputLen); + + if (result == GSASL_OK || result == GSASL_NEEDS_MORE) + { + byte* res = new byte[outputLen]; + + for (size_t i = 0 ; i < outputLen ; ++i) + res[i] = output[i]; + + *response = res; + *responseLen = outputLen; + + free(output); + } + else + { + *response = 0; + *responseLen = 0; + } + + if (result == GSASL_OK) + { + // Authentication process completed + m_complete = true; + return true; + } + else if (result == GSASL_NEEDS_MORE) + { + // Continue authentication process + return false; + } + else if (result == GSASL_MALLOC_ERROR) + { + throw std::bad_alloc(); + } + else + { + throw exceptions::sasl_exception("Error when processing challenge: " + + SASLContext::getErrorMessage("gsasl_step", result)); + } +} + + +const bool builtinSASLMechanism::isComplete() const +{ + return m_complete; +} + + +void builtinSASLMechanism::encode + (ref <SASLSession> sess, const byte* input, const int inputLen, + byte** output, int* outputLen) +{ + char* coutput = 0; + size_t coutputLen = 0; + + if (gsasl_encode(sess->m_gsaslSession, + reinterpret_cast <const char*>(input), inputLen, + &coutput, &coutputLen) != GSASL_OK) + { + throw exceptions::sasl_exception("Encoding error."); + } + + try + { + byte* res = new byte[coutputLen]; + + std::copy(coutput, coutput + coutputLen, res); + + *output = res; + *outputLen = static_cast <int>(coutputLen); + } + catch (...) + { + free(coutput); + throw; + } + + free(coutput); +} + + +void builtinSASLMechanism::decode + (ref <SASLSession> sess, const byte* input, const int inputLen, + byte** output, int* outputLen) +{ + char* coutput = 0; + size_t coutputLen = 0; + + try + { + if (gsasl_decode(sess->m_gsaslSession, + reinterpret_cast <const char*>(input), inputLen, + &coutput, &coutputLen) != GSASL_OK) + { + throw exceptions::sasl_exception("Decoding error."); + } + + byte* res = new byte[coutputLen]; + + std::copy(coutput, coutput + coutputLen, res); + + *output = res; + *outputLen = static_cast <int>(coutputLen); + } + catch (...) + { + free(coutput); + throw; + } + + free(coutput); +} + + +} // sasl +} // security +} // vmime + diff --git a/src/security/sasl/defaultSASLAuthenticator.cpp b/src/security/sasl/defaultSASLAuthenticator.cpp new file mode 100644 index 00000000..d396e069 --- /dev/null +++ b/src/security/sasl/defaultSASLAuthenticator.cpp @@ -0,0 +1,139 @@ +// +// 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/security/sasl/defaultSASLAuthenticator.hpp" + +#include "vmime/security/sasl/SASLMechanism.hpp" +#include "vmime/security/sasl/SASLSession.hpp" + +#include "vmime/net/service.hpp" + + +namespace vmime { +namespace security { +namespace sasl { + + +defaultSASLAuthenticator::defaultSASLAuthenticator() +{ +} + + +defaultSASLAuthenticator::~defaultSASLAuthenticator() +{ +} + + +const std::vector <ref <SASLMechanism> > + defaultSASLAuthenticator::getAcceptableMechanisms + (const std::vector <ref <SASLMechanism> >& available, + ref <SASLMechanism> suggested) const +{ + if (suggested) + { + std::vector <ref <SASLMechanism> > res; + + res.push_back(suggested); + + for (unsigned int i = 0 ; i < available.size() ; ++i) + { + if (available[i]->getName() != suggested->getName()) + res.push_back(available[i]); + } + + return res; + } + else + { + return available; + } +} + + +const string defaultSASLAuthenticator::getUsername() const +{ + return m_default.getUsername(); +} + + +const string defaultSASLAuthenticator::getPassword() const +{ + return m_default.getPassword(); +} + + +const string defaultSASLAuthenticator::getHostname() const +{ + return m_default.getHostname(); +} + + +const string defaultSASLAuthenticator::getAnonymousToken() const +{ + return m_default.getAnonymousToken(); +} + + +const string defaultSASLAuthenticator::getServiceName() const +{ + return m_saslSession->getServiceName(); +} + + +void defaultSASLAuthenticator::setService(ref <net::service> serv) +{ + m_service = serv; + m_default.setService(serv); +} + + +weak_ref <net::service> defaultSASLAuthenticator::getService() const +{ + return m_service; +} + + +void defaultSASLAuthenticator::setSASLSession(ref <SASLSession> sess) +{ + m_saslSession = sess; +} + + +ref <SASLSession> defaultSASLAuthenticator::getSASLSession() const +{ + return m_saslSession; +} + + +void defaultSASLAuthenticator::setSASLMechanism(ref <SASLMechanism> mech) +{ + m_saslMech = mech; +} + + +ref <SASLMechanism> defaultSASLAuthenticator::getSASLMechanism() const +{ + return m_saslMech; +} + + +} // sasl +} // security +} // vmime + diff --git a/src/utility/stream.cpp b/src/utility/stream.cpp index 49ebcf66..c13e8df7 100644 --- a/src/utility/stream.cpp +++ b/src/utility/stream.cpp @@ -326,6 +326,67 @@ const stream::size_type inputStreamPointerAdapter::skip(const size_type count) } + +// inputStreamByteBufferAdapter + +inputStreamByteBufferAdapter::inputStreamByteBufferAdapter(const byte* buffer, const size_type length) + : m_buffer(buffer), m_length(length), m_pos(0) +{ +} + + +const bool inputStreamByteBufferAdapter::eof() const +{ + return m_pos >= m_length; +} + + +void inputStreamByteBufferAdapter::reset() +{ + m_pos = 0; +} + + +const stream::size_type inputStreamByteBufferAdapter::read + (value_type* const data, const size_type count) +{ + const size_type remaining = m_length - m_pos; + + if (remaining < count) + { + std::copy(m_buffer + m_pos, m_buffer + m_pos + remaining, data); + m_pos += remaining; + + return remaining; + } + else + { + std::copy(m_buffer + m_pos, m_buffer + m_pos + count, data); + m_pos += count; + + return count; + } +} + + +const stream::size_type inputStreamByteBufferAdapter::skip(const size_type count) +{ + const size_type remaining = m_length - m_pos; + + if (remaining < count) + { + m_pos += remaining; + return remaining; + } + else + { + m_pos += count; + return count; + } +} + + + #ifdef VMIME_HAVE_MESSAGING_FEATURES |