aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/exception.cpp48
-rw-r--r--src/net/authenticationInfos.cpp52
-rw-r--r--src/net/authenticator.cpp33
-rw-r--r--src/net/defaultAuthenticator.cpp43
-rw-r--r--src/net/imap/IMAPConnection.cpp312
-rw-r--r--src/net/imap/IMAPFolder.cpp2
-rw-r--r--src/net/imap/IMAPStore.cpp80
-rw-r--r--src/net/maildir/maildirStore.cpp15
-rw-r--r--src/net/pop3/POP3Store.cpp426
-rw-r--r--src/net/sendmail/sendmailTransport.cpp13
-rw-r--r--src/net/service.cpp29
-rw-r--r--src/net/serviceFactory.cpp6
-rw-r--r--src/net/session.cpp16
-rw-r--r--src/net/simpleAuthenticator.cpp69
-rw-r--r--src/net/smtp/SMTPTransport.cpp406
-rw-r--r--src/net/transport.cpp2
-rw-r--r--src/security/defaultAuthenticator.cpp98
-rw-r--r--src/security/sasl/SASLContext.cpp189
-rw-r--r--src/security/sasl/SASLMechanismFactory.cpp131
-rw-r--r--src/security/sasl/SASLSession.cpp179
-rw-r--r--src/security/sasl/SASLSocket.cpp167
-rw-r--r--src/security/sasl/builtinSASLMechanism.cpp182
-rw-r--r--src/security/sasl/defaultSASLAuthenticator.cpp139
-rw-r--r--src/utility/stream.cpp61
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