diff --git a/ChangeLog b/ChangeLog index 317443b7..bcdea3a3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,11 @@ VERSION 0.7.2cvs ================ +2005-09-17 Vincent Richard + + * Added SASL support, based on GNU SASL library. Slightly modified + auhenticator object; see 'example6' which has been updated. + 2005-09-06 Vincent Richard * Created 'vmime::security' and 'vmime::security::digest' namespaces. diff --git a/SConstruct b/SConstruct index b4ff328d..0f24b6e8 100644 --- a/SConstruct +++ b/SConstruct @@ -170,12 +170,26 @@ libvmime_sources = [ # =============================== Misc =============================== 'misc/importanceHelper.cpp', 'misc/importanceHelper.hpp', # ============================= Security ============================= + 'security/authenticator.hpp', + 'security/defaultAuthenticator.cpp', 'security/defaultAuthenticator.hpp', + # -- digest 'security/digest/messageDigest.cpp', 'security/digest/messageDigest.hpp', 'security/digest/messageDigestFactory.cpp', 'security/digest/messageDigestFactory.hpp', 'security/digest/md5/md5MessageDigest.cpp', 'security/digest/md5/md5MessageDigest.hpp', 'security/digest/sha1/sha1MessageDigest.cpp', 'security/digest/sha1/sha1MessageDigest.hpp' ] +libvmime_security_sasl_sources = [ + 'security/sasl/SASLContext.cpp', 'security/sasl/SASLContext.hpp', + 'security/sasl/SASLSession.cpp', 'security/sasl/SASLSession.hpp', + 'security/sasl/SASLMechanism.hpp', + 'security/sasl/SASLMechanismFactory.cpp', 'security/sasl/SASLMechanismFactory.hpp', + 'security/sasl/SASLSocket.cpp', 'security/sasl/SASLSocket.hpp', + 'security/sasl/SASLAuthenticator.hpp', + 'security/sasl/defaultSASLAuthenticator.cpp', 'security/sasl/defaultSASLAuthenticator.hpp', + 'security/sasl/builtinSASLMechanism.cpp', 'security/sasl/builtinSASLMechanism.hpp' +] + libvmime_examples_sources = [ 'examples/README', # 'examples/Makefile.am', # not generated @@ -190,11 +204,8 @@ libvmime_examples_sources = [ ] libvmime_messaging_sources = [ - 'net/authenticator.cpp', 'net/authenticator.hpp', - 'net/authenticationInfos.cpp', 'net/authenticationInfos.hpp', 'net/authHelper.cpp', 'net/authHelper.hpp', 'net/builtinServices.inl', - 'net/defaultAuthenticator.cpp', 'net/defaultAuthenticator.hpp', 'net/events.cpp', 'net/events.hpp', 'net/folder.cpp', 'net/folder.hpp', 'net/message.cpp', 'net/message.hpp', @@ -202,7 +213,6 @@ libvmime_messaging_sources = [ 'net/serviceFactory.cpp', 'net/serviceFactory.hpp', 'net/serviceInfos.cpp', 'net/serviceInfos.hpp', 'net/session.cpp', 'net/session.hpp', - 'net/simpleAuthenticator.cpp', 'net/simpleAuthenticator.hpp', 'net/socket.hpp', 'net/store.hpp', 'net/timeoutHandler.hpp', @@ -357,7 +367,7 @@ libvmime_autotools = [ 'vmime/Makefile.in' ] -libvmime_all_sources = [] + libvmime_sources + libvmime_messaging_sources +libvmime_all_sources = [] + libvmime_sources + libvmime_messaging_sources + libvmime_security_sasl_sources for i in range(len(libvmime_all_sources)): f = libvmime_all_sources[i] @@ -463,6 +473,14 @@ opts.AddOptions( + 'Currently available platform handlers: posix.', '"posix"' ), + EnumOption( + 'with_sasl', + 'Enable SASL support (requires GNU SASL library)', + 'yes', + allowed_values = ('yes', 'no'), + map = { }, + ignorecase = 1 + ), ( 'sendmail_path', 'Specifies the path to sendmail.', @@ -554,6 +572,8 @@ else: #env.Append(LIBS = ['additional-lib-here']) +env.ParseConfig('pkg-config --cflags --libs libgsasl') + # Generate help text for command line options Help(opts.GenerateHelpText(env)) @@ -642,6 +662,7 @@ if env['with_messaging'] == 'yes': print " * protocols : " + env['with_messaging_protocols'] print "File-system support : " + env['with_filesystem'] print "Platform handlers : " + env['with_platforms'] +print "SASL support : " + env['with_sasl'] if IsProtocolSupported(messaging_protocols, 'sendmail'): print "Sendmail path : " + env['sendmail_path'] @@ -727,6 +748,12 @@ if env['with_filesystem'] == 'yes': else: config_hpp.write('#define VMIME_HAVE_FILESYSTEM_FEATURES 0\n') +config_hpp.write('// -- SASL support\n') +if env['with_sasl'] == 'yes': + config_hpp.write('#define VMIME_HAVE_SASL_SUPPORT 1\n') +else: + config_hpp.write('#define VMIME_HAVE_SASL_SUPPORT 0\n') + config_hpp.write('// -- Messaging support\n') if env['with_messaging'] == 'yes': config_hpp.write('#define VMIME_HAVE_MESSAGING_FEATURES 1\n') @@ -800,6 +827,11 @@ if env['with_messaging'] == 'yes': for file in protosrc[1]: libvmime_sel_sources.append(file) +# -- SASL support +if env['with_sasl'] == 'yes': + for file in libvmime_security_sasl_sources: + libvmime_sel_sources.append(file) + # -- platform handlers for platform in platforms: files = libvmime_platforms_sources[platform] @@ -852,12 +884,13 @@ Default(libVmime) # Tests if env['build_tests'] == 'yes': if env['debug'] == 'yes': + env = env.Copy() + env.Append(LIBS = ['cppunit', 'dl', packageVersionedGenericName + '-debug']) + env.Append(LIBPATH=['.']) Default( env.Program( target = 'run-tests', - source = libvmimetest_sources, - LIBS=['cppunit', 'dl', packageVersionedGenericName + '-debug'], - LIBPATH=['.'] + source = libvmimetest_sources ) ) else: @@ -888,6 +921,13 @@ env.Install(includeDir, 'vmime/config.hpp') # Pkg-config support vmime_pc = open(packageVersionedGenericName + ".pc", 'w') +vmime_pc_requires = '' +vmime_pc_libs = '' + +if env['with_sasl'] == 'yes': + vmime_pc_requires = vmime_pc_requires + "libgsasl " + vmime_pc_libs = vmime_pc_libs + "-lgsasl " + vmime_pc.write("prefix=" + env['prefix'] + "\n") vmime_pc.write("exec_prefix=" + env['prefix'] + "\n") vmime_pc.write("libdir=" + env['prefix'] + "/lib\n") @@ -896,8 +936,8 @@ vmime_pc.write("\n") vmime_pc.write("Name: " + packageRealName + "\n") vmime_pc.write("Description: " + packageDescription + "\n") vmime_pc.write("Version: " + packageVersion + "\n") -vmime_pc.write("Requires:\n") -vmime_pc.write("Libs: -L${libdir} -l" + packageVersionedGenericName + "\n") +vmime_pc.write("Requires: " + vmime_pc_requires + "\n") +vmime_pc.write("Libs: -L${libdir} -l" + packageVersionedGenericName + " " + vmime_pc_libs + "\n") #vmime_pc.write("Cflags: -I${includedir}/" + packageVersionedGenericName + "\n") vmime_pc.write("Cflags: -I${includedir}/" + "\n") @@ -969,8 +1009,8 @@ def generateAutotools(target, source, env): vmime_pc_in.write("Name: @GENERIC_LIBRARY_NAME@\n") vmime_pc_in.write("Description: " + packageDescription + "\n") vmime_pc_in.write("Version: @VERSION@\n") - vmime_pc_in.write("Requires:\n") - vmime_pc_in.write("Libs: -L${libdir} -l@GENERIC_VERSIONED_LIBRARY_NAME@\n") + vmime_pc_in.write("Requires: @GSASL_REQUIRED@\n") + vmime_pc_in.write("Libs: -L${libdir} -l@GENERIC_VERSIONED_LIBRARY_NAME@ @GSASL_LIBS@\n") #vmime_pc_in.write("Cflags: -I${includedir}/@GENERIC_VERSIONED_LIBRARY_NAME@\n") vmime_pc_in.write("Cflags: -I${includedir}/\n") vmime_pc_in.close() @@ -1074,6 +1114,15 @@ INCLUDES = -I$(top_srcdir) -I$(srcdir) @PKGCONFIG_CFLAGS@ @EXTRA_CFLAGS@ Makefile_am.write(packageVersionedName + "_la_SOURCES += " + buildMakefileFileList(x, 1) + "\n") Makefile_am.write("endif\n") + # -- SASL support + x = selectFilesFromSuffixNot(libvmime_security_sasl_sources, '.hpp') + sourceFiles += x + + Makefile_am.write("\n") + Makefile_am.write("if VMIME_HAVE_SASL_SUPPORT\n") + Makefile_am.write(packageVersionedName + "_la_SOURCES += " + buildMakefileFileList(x, 1) + "\n") + Makefile_am.write("endif\n") + # -- platform handlers for platform in libvmime_platforms_sources: Makefile_am.write("\n") @@ -1200,6 +1249,13 @@ else AC_ERROR(no usable version of iconv has been found) fi +# -- GNU SASL Library (http://www.gnu.org/software/gsasl/) +AC_CHECK_HEADER(gsasl.h, + AC_CHECK_LIB(gsasl, gsasl_check_version, + [have_gsasl=yes AC_SUBST(GSASL_AVAIL_LIBS, -lgsasl) AC_SUBST(GSASL_AVAIL_REQUIRED, libgsasl)], + have_gsasl=no), + have_gsasl=no) + # -- global constructors (stolen from 'configure.in' in libsigc++) AC_MSG_CHECKING([if linker supports global constructors]) cat > mylib.$ac_ext < #include #include +#include #include "vmime/vmime.hpp" #include "vmime/platforms/posix/posixHandler.hpp" @@ -31,27 +32,65 @@ static vmime::ref g_session // Authentification handler -class interactiveAuthenticator : public vmime::net::authenticator +class interactiveAuthenticator : public vmime::security::sasl::defaultSASLAuthenticator { - const vmime::net::authenticationInfos requestAuthInfos() const + const std::vector > getAcceptableMechanisms + (const std::vector >& available, + vmime::ref suggested) const { - vmime::string username, password; + std::cout << std::endl << "Available SASL mechanisms:" << std::endl; - std::cout << std::endl; - std::cout << "Please authenticate yourself:" << std::endl; + for (unsigned int i = 0 ; i < available.size() ; ++i) + { + std::cout << " " << available[i]->getName(); - std::cout << " Username: "; - std::cout.flush(); + if (suggested && available[i]->getName() == suggested->getName()) + std::cout << "(suggested)"; + } - std::getline(std::cin, username); + std::cout << std::endl << std::endl; - std::cout << " Password: "; - std::cout.flush(); - - std::getline(std::cin, password); - - return (vmime::net::authenticationInfos(username, password)); + return defaultSASLAuthenticator::getAcceptableMechanisms(available, suggested); } + + void setSASLMechanism(vmime::ref mech) + { + std::cout << "Trying " << mech->getName() << std::endl; + + defaultSASLAuthenticator::setSASLMechanism(mech); + } + + const vmime::string getUsername() const + { + if (m_username.empty()) + m_username = getUserInput("Username"); + + return m_username; + } + + const vmime::string getPassword() const + { + if (m_password.empty()) + m_password = getUserInput("Password"); + + return m_password; + } + + static const vmime::string getUserInput(const std::string& prompt) + { + std::cout << prompt << ": "; + std::cout.flush(); + + vmime::string res; + std::getline(std::cin, res); + + return res; + } + +private: + + mutable vmime::string m_username; + mutable vmime::string m_password; }; @@ -215,7 +254,7 @@ static void sendMessage() // You can also set some properties (see example7 to know the properties // available for each service). For example, for SMTP: -// tr->setProperty("options.need-authentication", true); + tr->setProperty("options.need-authentication", true); // Information about the mail std::cout << "Enter email of the expeditor (eg. me@somewhere.com): "; 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 -// -// 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 -// -// 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 -// -// 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 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 @@ -42,7 +46,7 @@ namespace net { namespace imap { -IMAPConnection::IMAPConnection(weak_ref store, ref auth) +IMAPConnection::IMAPConnection(weak_ref store, ref 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 store, ref 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,25 +118,14 @@ void IMAPConnection::connect() } else if (greet->resp_cond_auth()->condition() != IMAPParser::resp_cond_auth::PREAUTH) { - const authenticationInfos auth = m_auth->requestAuthInfos(); - - // TODO: other authentication methods - - send(true, "LOGIN " + IMAPUtils::quoteString(auth.getUsername()) - + " " + IMAPUtils::quoteString(auth.getPassword()), true); - - utility::auto_ptr resp(m_parser->readResponse()); - - if (resp->isBad()) + try { - internalDisconnect(); - throw exceptions::command_error("LOGIN", m_parser->lastLine()); + authenticate(); } - else if (resp->response_done()->response_tagged()-> - resp_cond_state()->status() != IMAPParser::resp_cond_state::OK) + catch (...) { - internalDisconnect(); - throw exceptions::authentication_error(m_parser->lastLine()); + m_state = STATE_NONE; + throw; } } @@ -137,6 +137,278 @@ void IMAPConnection::connect() } +void IMAPConnection::authenticate() +{ + getAuthenticator()->setService(thisRef().dynamicCast ()); + +#if VMIME_HAVE_SASL_SUPPORT + // First, try SASL authentication + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_SASL)) + { + try + { + 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 + } + } + catch (exception& e) + { + internalDisconnect(); + throw e; + } + } +#endif // VMIME_HAVE_SASL_SUPPORT + + // Normal authentication + const string username = getAuthenticator()->getUsername(); + const string password = getAuthenticator()->getPassword(); + + send(true, "LOGIN " + IMAPUtils::quoteString(username) + + " " + IMAPUtils::quoteString(password), true); + + utility::auto_ptr 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 ()) + throw exceptions::authentication_error("No SASL authenticator available."); + + const std::vector capa = getCapabilities(); + std::vector 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 > mechList; + + ref saslContext = + vmime::create (); + + for (unsigned int i = 0 ; i < saslMechs.size() ; ++i) + { + try + { + mechList.push_back + (saslContext->createMechanism(saslMechs[i])); + } + catch (exceptions::no_such_mechanism&) + { + // Ignore mechanism + } + } + + if (mechList.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + // Try to suggest a mechanism among all those supported + ref 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 ()-> + 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 mech = mechList[i]; + + ref saslSession = + saslContext->createSession("imap", getAuthenticator(), mech); + + saslSession->init(); + + send(true, "AUTHENTICATE " + mech->getName(), true); + + for (bool cont = true ; cont ; ) + { + utility::auto_ptr 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 + 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 IMAPConnection::getCapabilities() +{ + send(true, "CAPABILITY", true); + + utility::auto_ptr resp(m_parser->readResponse()); + + std::vector res; + + if (resp->response_done()->response_tagged()-> + resp_cond_state()->status() == IMAPParser::resp_cond_state::OK) + { + const std::vector & 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 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 IMAPConnection::getAuthenticator() +{ + return m_auth; +} + + const bool IMAPConnection::isConnected() const { return (m_socket && m_socket->isConnected() && 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 connection = - vmime::create (m_store, m_store->oneTimeAuthenticator()); + vmime::create (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 -{ -public: - - IMAPauthenticator(ref auth) - : m_auth(auth), m_infos(NULL) - { - } - - ~IMAPauthenticator() - { - } - - const authenticationInfos requestAuthInfos() const - { - if (m_infos == NULL) - m_infos = vmime::create (m_auth->requestAuthInfos()); - - return (*m_infos); - } - -private: - - ref m_auth; - mutable ref m_infos; -}; - -#endif // VMIME_BUILDING_DOC - - - -// -// IMAPStore -// - -IMAPStore::IMAPStore(ref sess, ref auth) - : store(sess, getInfosInstance(), auth), - m_connection(NULL), m_oneTimeAuth(NULL) +IMAPStore::IMAPStore(ref sess, ref auth) + : store(sess, getInfosInstance(), auth), m_connection(NULL) { } IMAPStore::~IMAPStore() { - if (isConnected()) - disconnect(); -} - - -ref IMAPStore::oneTimeAuthenticator() -{ - return (m_oneTimeAuth); + try + { + if (isConnected()) + disconnect(); + } + catch (vmime::exception&) + { + // Ignore + } } @@ -140,10 +96,8 @@ void IMAPStore::connect() if (isConnected()) throw exceptions::already_connected(); - m_oneTimeAuth = vmime::create (getAuthenticator()); - m_connection = vmime::create - (thisWeakRef().dynamicCast (), m_oneTimeAuth); + (thisWeakRef().dynamicCast (), 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 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 sess, ref auth) +maildirStore::maildirStore(ref sess, ref auth) : store(sess, getInfosInstance(), auth), m_connected(false) { } @@ -47,8 +47,15 @@ maildirStore::maildirStore(ref sess, ref 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 @@ -41,7 +46,7 @@ namespace net { namespace pop3 { -POP3Store::POP3Store(ref sess, ref auth) +POP3Store::POP3Store(ref sess, ref auth) : store(sess, getInfosInstance(), auth), m_socket(NULL), m_authentified(false), m_timeoutHandler(NULL) { @@ -50,10 +55,17 @@ POP3Store::POP3Store(ref sess, ref 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)) - { - bool authentified = false; - - const authenticationInfos auth = getAuthenticator()->requestAuthInfos(); - - // Secured authentication with APOP (if requested and if available) - // - // eg: C: APOP vincent - // --- S: +OK vincent is a valid mailbox - messageId mid(response); - - if (GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP)) - { - if (mid.getLeft().length() && mid.getRight().length()) - { - // is the result of MD5 applied to "password" - ref md5 = - security::digest::messageDigestFactory::getInstance()->create("md5"); - - md5->update(mid.generate() + auth.getPassword()); - md5->finalize(); - - sendRequest("APOP " + auth.getUsername() + " " + md5->getHexDigest()); - readResponse(response, false); - - if (isSuccessResponse(response)) - { - authentified = true; - } - else - { - if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) - { - internalDisconnect(); - throw exceptions::authentication_error(response); - } - } - } - else - { - // APOP not supported - if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) - { - // Can't fallback on basic authentification - internalDisconnect(); - throw exceptions::unsupported_option(); - } - } - } - - if (!authentified) - { - // 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()); - readResponse(response, false); - - if (isSuccessResponse(response)) - { - sendRequest("PASS " + auth.getPassword()); - readResponse(response, false); - - if (!isSuccessResponse(response)) - { - internalDisconnect(); - throw exceptions::authentication_error(response); - } - } - else - { - internalDisconnect(); - throw exceptions::authentication_error(response); - } - } - } - else + if (!isSuccessResponse(response)) { internalDisconnect(); throw exceptions::connection_greeting_error(response); } + // Start authentication process + authenticate(messageId(response)); +} + + +void POP3Store::authenticate(const messageId& randomMID) +{ + getAuthenticator()->setService(thisRef().dynamicCast ()); + +#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 APOP/normal authentication + internalDisconnect(); + throw e; + } + else + { + // 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 + // --- S: +OK vincent is a valid mailbox + + const string username = getAuthenticator()->getUsername(); + const string password = getAuthenticator()->getPassword(); + + string response; + + if (GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP)) + { + if (randomMID.getLeft().length() != 0 && + randomMID.getRight().length() != 0) + { + // is the result of MD5 applied to "password" + ref 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)) + { + 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 (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) + { + // Can't fallback on basic authentication + internalDisconnect(); + throw exceptions::authentication_error(response); + } + } + } + else + { + // APOP not supported + if (!GET_PROPERTY(bool, PROPERTY_OPTIONS_APOP_FALLBACK)) + { + // Can't fallback on basic authentication + internalDisconnect(); + throw exceptions::authentication_error("APOP not supported"); + } + } + } + + // 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::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 ()) + throw exceptions::authentication_error("No SASL authenticator available."); + + std::vector capa = getCapabilities(); + std::vector 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 > mechList; + + ref saslContext = + vmime::create (); + + for (unsigned int i = 0 ; i < saslMechs.size() ; ++i) + { + try + { + mechList.push_back + (saslContext->createMechanism(saslMechs[i])); + } + catch (exceptions::no_such_mechanism&) + { + // Ignore mechanism + } + } + + if (mechList.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + // Try to suggest a mechanism among all those supported + ref 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 ()-> + 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 mech = mechList[i]; + + ref 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 std::vector POP3Store::getCapabilities() +{ + sendRequest("CAPA"); + + string response; + readResponse(response, true); + + std::vector res; + + 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) { - static const string OK("+OK"); - - return (buffer.length() >= 3 && - std::equal(buffer.begin(), buffer.begin() + 3, OK.begin())); + 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 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 sess, ref auth) +sendmailTransport::sendmailTransport(ref sess, ref auth) : transport(sess, getInfosInstance(), auth), m_connected(false) { } @@ -54,8 +54,15 @@ sendmailTransport::sendmailTransport(ref sess, ref 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 sess, const serviceInfos& infos, ref auth) +service::service(ref sess, const serviceInfos& /* infos */, + ref auth) : m_session(sess), m_auth(auth) { if (!auth) { - m_auth = vmime::create - (sess, infos.getPropertyPrefix()); +#if VMIME_HAVE_SASL_SUPPORT + m_auth = vmime::create + (); +#else + m_auth = vmime::create + (); +#endif // VMIME_HAVE_SASL_SUPPORT } } @@ -54,17 +65,23 @@ ref service::getSession() } -ref service::getAuthenticator() const +ref service::getAuthenticator() const { return (m_auth); } -ref service::getAuthenticator() +ref service::getAuthenticator() { return (m_auth); } +void service::setAuthenticator(ref 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 serviceFactory::create - (ref sess, const string& protocol, ref auth) + (ref sess, const string& protocol, + ref auth) { return (getServiceByProtocol(protocol)->create(sess, auth)); } ref serviceFactory::create - (ref sess, const utility::url& u, ref auth) + (ref sess, const utility::url& u, + ref auth) { ref 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 session::getTransport(ref auth) +ref session::getTransport(ref auth) { return (getTransport(m_props["transport.protocol"], auth)); } -ref session::getTransport(const string& protocol, ref auth) +ref session::getTransport + (const string& protocol, ref auth) { ref sess = thisRef().dynamicCast (); ref sv = serviceFactory::getInstance()->create(sess, protocol, auth); @@ -68,7 +69,8 @@ ref session::getTransport(const string& protocol, ref session::getTransport(const utility::url& url, ref auth) +ref session::getTransport + (const utility::url& url, ref auth) { ref sess = thisRef().dynamicCast (); ref sv = serviceFactory::getInstance()->create(sess, url, auth); @@ -80,13 +82,14 @@ ref session::getTransport(const utility::url& url, ref session::getStore(ref auth) +ref session::getStore(ref auth) { return (getStore(m_props["store.protocol"], auth)); } -ref session::getStore(const string& protocol, ref auth) +ref session::getStore + (const string& protocol, ref auth) { ref sess = thisRef().dynamicCast (); ref sv = serviceFactory::getInstance()->create(sess, protocol, auth); @@ -98,7 +101,8 @@ ref session::getStore(const string& protocol, ref auth) } -ref session::getStore(const utility::url& url, ref auth) +ref session::getStore + (const utility::url& url, ref auth) { ref sess = thisRef().dynamicCast (); ref 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 -// -// 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 sess, ref auth) +SMTPTransport::SMTPTransport(ref sess, ref auth) : transport(sess, getInfosInstance(), auth), m_socket(NULL), m_authentified(false), m_extendedSMTP(false), m_timeoutHandler(NULL) { @@ -50,10 +55,17 @@ SMTPTransport::SMTPTransport(ref sess, ref 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: // --- 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 ()); + +#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 + +#if VMIME_HAVE_SASL_SUPPORT + +void SMTPTransport::authenticateSASL() +{ + if (!getAuthenticator().dynamicCast ()) + throw exceptions::authentication_error("No SASL authenticator available."); + + // Obtain SASL mechanisms supported by server from EHLO response + std::vector 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) { - First = 0, - CRAM_MD5 = First, - // TODO: more authentication methods... - End - }; - - for (int currentMethod = First ; !authentified ; ++currentMethod) - { - 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: - { - sendRequest("AUTH CRAM-MD5"); - readResponse(response); - - if (responseCode(response) == 334) - { - encoderB64 base64; - - string challengeB64 = responseText(response); - string challenge, challengeHex; - - { - utility::inputStreamStringAdapter in(challengeB64); - utility::outputStreamStringAdapter out(challenge); - - base64.decode(in, out); - } - - hmac_md5(challenge, auth.getPassword(), challengeHex); - - string decoded = auth.getUsername() + " " + challengeHex; - string encoded; - - { - utility::inputStreamStringAdapter in(decoded); - utility::outputStreamStringAdapter out(encoded); - - base64.encode(in, out); - } - - sendRequest(encoded); - readResponse(response); - - if (responseCode(response) == 235) - { - authentified = true; - } - else - { - internalDisconnect(); - throw exceptions::authentication_error(response); - } - } - - break; + inAuth = true; } - case End: + else if (inAuth) { - // All authentication methods have been tried and - // the server does not understand any. - throw exceptions::authentication_error(response); - } - + saslMechs.push_back(word); } } } - m_authentified = true; + if (saslMechs.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + std::vector > mechList; + + ref saslContext = + vmime::create (); + + for (unsigned int i = 0 ; i < saslMechs.size() ; ++i) + { + try + { + mechList.push_back + (saslContext->createMechanism(saslMechs[i])); + } + catch (exceptions::no_such_mechanism&) + { + // Ignore mechanism + } + } + + if (mechList.empty()) + throw exceptions::authentication_error("No SASL mechanism available."); + + // Try to suggest a mechanism among all those supported + ref 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 ()-> + 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 mech = mechList[i]; + + ref 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) + { + 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 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; + // Get a line from the response buffer + string::size_type lineEnd = currentBuffer.find_first_of('\n'); - text += "\n"; - } + if (lineEnd != string::npos) + { + const string line(currentBuffer.begin(), currentBuffer.begin() + lineEnd); - return (text); -} + currentBuffer.erase(currentBuffer.begin(), currentBuffer.begin() + lineEnd + 1); + m_responseBuffer = currentBuffer; + return line; + } -void SMTPTransport::readResponse(string& buffer) -{ - bool foundTerminator = false; - - buffer.clear(); - - 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') +const int SMTPTransport::readResponse(string& text) +{ + string line = readResponseLine(); + + // Special case where CRLF occurs after response code + if (line.length() < 4) + line = line + '\n' + readResponseLine(); + + 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); + + 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) + { + const int code = readResponse(outText); + + if (allText) + text += '\n' + outText; + + if (code != firstCode) { - string::size_type p = buffer.length() - 2; - bool end = false; + if (allText) + outText = text; - for ( ; !end ; --p) - { - if (p == 0 || buffer[p] == '\n') - { - end = true; - - if (p + 4 < buffer.length()) - foundTerminator = true; - } - } + return 0; } } - // Remove [CR]LF at the end of the response - if (buffer.length() >= 2 && buffer[buffer.length() - 1] == '\n') - { - if (buffer[buffer.length() - 2] == '\r') - buffer.resize(buffer.length() - 2); - else - buffer.resize(buffer.length() - 1); - } + 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 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 sess, const serviceInfos& infos, ref auth) +transport::transport(ref sess, const serviceInfos& infos, ref 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 +// +// 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 serv) +{ + m_service = serv; +} + + +weak_ref 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 +// +// 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 + +#include + +#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 SASLContext::createSession + (const string& serviceName, + ref auth, ref mech) +{ + return vmime::create + (serviceName, thisRef().dynamicCast (), auth, mech); +} + + +ref SASLContext::createMechanism(const string& name) +{ + return SASLMechanismFactory::getInstance()->create + (thisRef().dynamicCast (), name); +} + + +ref SASLContext::suggestMechanism + (const std::vector >& 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 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 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 +// +// 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 +#include + +#include + +#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 SASLMechanismFactory::create + (ref ctx, const string& name_) +{ + const string name(utility::stringUtils::toUpper(name_)); + + // Check for built-in mechanisms + if (isMechanismSupported(name)) + { + return vmime::create (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 SASLMechanismFactory::getSupportedMechanisms() const +{ + std::vector 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 +// +// 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 + +#include + +#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 ctx, + ref auth, ref 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 saslAuth = m_auth.dynamicCast (); + + if (saslAuth) + { + saslAuth->setSASLMechanism(m_mech); + saslAuth->setSASLSession(thisRef().dynamicCast ()); + } +} + + +ref SASLSession::getAuthenticator() +{ + return m_auth; +} + + +ref SASLSession::getMechanism() +{ + return m_mech; +} + + +ref 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 (), + challenge, challengeLen, response, responseLen); +} + + +ref SASLSession::getSecuredSocket(ref sok) +{ + return vmime::create (thisRef().dynamicCast (), 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 (gsasl_callback_hook_get(ctx)); + if (!sess) return GSASL_AUTHENTICATION_ERROR; + + ref 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 +// +// 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 + +#include + + +namespace vmime { +namespace security { +namespace sasl { + + + +SASLSocket::SASLSocket(ref sess, ref 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 (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 (buffer), count, + &output, &outputLen); + + try + { + m_wrapped->sendRaw + (reinterpret_cast (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 +// +// 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 + +#include "vmime/security/sasl/builtinSASLMechanism.hpp" + +#include "vmime/security/sasl/SASLContext.hpp" +#include "vmime/security/sasl/SASLSession.hpp" + +#include "vmime/exception.hpp" + +#include +#include + + +namespace vmime { +namespace security { +namespace sasl { + + +builtinSASLMechanism::builtinSASLMechanism(ref 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 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 (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 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 (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 (coutputLen); + } + catch (...) + { + free(coutput); + throw; + } + + free(coutput); +} + + +void builtinSASLMechanism::decode + (ref 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 (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 (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 +// +// 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 > + defaultSASLAuthenticator::getAcceptableMechanisms + (const std::vector >& available, + ref suggested) const +{ + if (suggested) + { + std::vector > 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 serv) +{ + m_service = serv; + m_default.setService(serv); +} + + +weak_ref defaultSASLAuthenticator::getService() const +{ + return m_service; +} + + +void defaultSASLAuthenticator::setSASLSession(ref sess) +{ + m_saslSession = sess; +} + + +ref defaultSASLAuthenticator::getSASLSession() const +{ + return m_saslSession; +} + + +void defaultSASLAuthenticator::setSASLMechanism(ref mech) +{ + m_saslMech = mech; +} + + +ref 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 diff --git a/vmime/exception.hpp b/vmime/exception.hpp index d7f6912d..d7dc5564 100644 --- a/vmime/exception.hpp +++ b/vmime/exception.hpp @@ -811,6 +811,58 @@ public: #endif // VMIME_HAVE_FILESYSTEM_FEATURES +#if VMIME_HAVE_SASL_SUPPORT + + +/** Base class for exceptions throw by SASL module. + */ + +class sasl_exception : public vmime::exception +{ +public: + + sasl_exception(const string& what, const exception& other = NO_EXCEPTION); + ~sasl_exception() throw(); + + exception* clone() const; + const char* name() const throw(); +}; + + +/** No mechanism is registered with the specified name. + */ + +class no_such_mechanism : public sasl_exception +{ +public: + + no_such_mechanism(const string& name, const exception& other = NO_EXCEPTION); + ~no_such_mechanism() throw(); + + exception* clone() const; + const char* name() const throw(); +}; + + +/** The requested information cannot be provided. + */ + +class no_auth_information : public sasl_exception +{ +public: + + no_auth_information(const exception& other = NO_EXCEPTION); + ~no_auth_information() throw(); + + exception* clone() const; + const char* name() const throw(); +}; + + +#endif // VMIME_HAVE_SASL_SUPPORT + + + } // exceptions diff --git a/vmime/net/authenticator.hpp b/vmime/net/authenticator.hpp deleted file mode 100644 index 7e58a70b..00000000 --- a/vmime/net/authenticator.hpp +++ /dev/null @@ -1,54 +0,0 @@ -// -// VMime library (http://www.vmime.org) -// Copyright (C) 2002-2005 Vincent Richard -// -// 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. -// - -#ifndef VMIME_NET_AUTHENTICATOR_HPP_INCLUDED -#define VMIME_NET_AUTHENTICATOR_HPP_INCLUDED - - -#include "vmime/types.hpp" -#include "vmime/net/authenticationInfos.hpp" - - -namespace vmime { -namespace net { - - -/** This class is used to obtain user credentials. - */ - -class authenticator : public object -{ -public: - - virtual ~authenticator(); - - /** Called when the service needs to retrieve user credentials. - * It should return the user account name and password. - * - * @return user credentials (user name and password) - */ - virtual const authenticationInfos requestAuthInfos() const = 0; -}; - - -} // net -} // vmime - - -#endif // VMIME_NET_AUTHENTICATOR_HPP_INCLUDED diff --git a/vmime/net/imap/IMAPConnection.hpp b/vmime/net/imap/IMAPConnection.hpp index 542f4869..87eb80e0 100644 --- a/vmime/net/imap/IMAPConnection.hpp +++ b/vmime/net/imap/IMAPConnection.hpp @@ -23,13 +23,14 @@ #include "vmime/config.hpp" -#include "vmime/net/authenticator.hpp" #include "vmime/net/socket.hpp" #include "vmime/net/timeoutHandler.hpp" #include "vmime/net/session.hpp" #include "vmime/net/imap/IMAPParser.hpp" +#include "vmime/security/authenticator.hpp" + namespace vmime { namespace net { @@ -44,7 +45,7 @@ class IMAPConnection : public object { public: - IMAPConnection(weak_ref store, ref auth); + IMAPConnection(weak_ref store, ref auth); ~IMAPConnection(); @@ -83,11 +84,21 @@ public: ref getSession(); + const std::vector getCapabilities(); + + ref getAuthenticator(); + private: + void authenticate(); +#if VMIME_HAVE_SASL_SUPPORT + void authenticateSASL(); +#endif // VMIME_HAVE_SASL_SUPPORT + + weak_ref m_store; - ref m_auth; + ref m_auth; ref m_socket; diff --git a/vmime/net/imap/IMAPParser.hpp b/vmime/net/imap/IMAPParser.hpp index 090e6ffe..14d10b57 100644 --- a/vmime/net/imap/IMAPParser.hpp +++ b/vmime/net/imap/IMAPParser.hpp @@ -1401,22 +1401,32 @@ public: string::size_type pos = *currentPos; - parser.check >(line, &pos); + if (parser.check >(line, &pos, true)) + { + atom* at = parser.get (line, &pos); + const string name = utility::stringUtils::toLower(at->value()); + delete (at); - atom* at = parser.get (line, &pos); - const string name = utility::stringUtils::toLower(at->value()); - delete (at); - - if (name == "marked") - m_type = MARKED; - else if (name == "noinferiors") - m_type = NOINFERIORS; - else if (name == "noselect") - m_type = NOSELECT; - else if (name == "unmarked") - m_type = UNMARKED; + if (name == "marked") + m_type = MARKED; + else if (name == "noinferiors") + m_type = NOINFERIORS; + else if (name == "noselect") + m_type = NOSELECT; + else if (name == "unmarked") + m_type = UNMARKED; + else + { + m_type = UNKNOWN; + m_name = "\\" + name; + } + } else { + atom* at = parser.get (line, &pos); + const string name = utility::stringUtils::toLower(at->value()); + delete (at); + m_type = UNKNOWN; m_name = name; } @@ -1989,40 +1999,13 @@ public: string::size_type pos = *currentPos; parser.checkWithArg (line, &pos, "capability"); - parser.check (line, &pos); - bool IMAP4rev1 = false; - - for (bool end = false ; !end && !IMAP4rev1 ; ) + while (parser.check (line, &pos, true)) { - if (parser.checkWithArg (line, &pos, "imap4rev1", true)) - { - IMAP4rev1 = true; - } - else - { - capability* cap = parser.get (line, &pos); - end = (cap == NULL); + capability* cap = parser.get (line, &pos); + if (cap == NULL) break; - if (cap) - { - m_capabilities.push_back(cap); - } - } - - parser.check (line, &pos); - } - - - if (parser.check (line, &pos, true)) - { - for (capability* cap = NULL ; - (cap = parser.get (line, &pos)) != NULL ; ) - { - m_capabilities.push_back(cap); - - parser.check (line, &pos); - } + m_capabilities.push_back(cap); } *currentPos = pos; diff --git a/vmime/net/imap/IMAPStore.hpp b/vmime/net/imap/IMAPStore.hpp index 887894f4..506ba42f 100644 --- a/vmime/net/imap/IMAPStore.hpp +++ b/vmime/net/imap/IMAPStore.hpp @@ -52,7 +52,7 @@ class IMAPStore : public store public: - IMAPStore(ref sess, ref auth); + IMAPStore(ref sess, ref auth); ~IMAPStore(); const string getProtocolName() const; @@ -79,13 +79,6 @@ private: // Connection ref m_connection; - // Used to request the authentication informations only the - // first time, and reuse these informations the next time. - ref m_oneTimeAuth; - - - - ref oneTimeAuthenticator(); ref connection(); @@ -106,7 +99,10 @@ private: struct props { // IMAP-specific options - // (none) +#if VMIME_HAVE_SASL_SUPPORT + serviceInfos::property PROPERTY_OPTIONS_SASL; + serviceInfos::property PROPERTY_OPTIONS_SASL_FALLBACK; +#endif // VMIME_HAVE_SASL_SUPPORT // Common properties serviceInfos::property PROPERTY_AUTH_USERNAME; diff --git a/vmime/net/maildir/maildirStore.hpp b/vmime/net/maildir/maildirStore.hpp index 32451808..bfaf0487 100644 --- a/vmime/net/maildir/maildirStore.hpp +++ b/vmime/net/maildir/maildirStore.hpp @@ -49,7 +49,7 @@ class maildirStore : public store public: - maildirStore(ref sess, ref auth); + maildirStore(ref sess, ref auth); ~maildirStore(); const string getProtocolName() const; diff --git a/vmime/net/pop3/POP3Store.hpp b/vmime/net/pop3/POP3Store.hpp index e50bbeef..e5ce7ee2 100644 --- a/vmime/net/pop3/POP3Store.hpp +++ b/vmime/net/pop3/POP3Store.hpp @@ -48,7 +48,7 @@ class POP3Store : public store public: - POP3Store(ref sess, ref auth); + POP3Store(ref sess, ref auth); ~POP3Store(); const string getProtocolName() const; @@ -72,9 +72,24 @@ public: private: + enum ResponseCode + { + RESPONSE_OK = 0, + RESPONSE_READY, + RESPONSE_ERR + }; + + void authenticate(const messageId& randomMID); +#if VMIME_HAVE_SASL_SUPPORT + void authenticateSASL(); +#endif // VMIME_HAVE_SASL_SUPPORT + + const std::vector getCapabilities(); + static const bool isSuccessResponse(const string& buffer); static const bool stripFirstLine(const string& buffer, string& result, string* firstLine = NULL); static void stripResponseCode(const string& buffer, string& result); + static const int getResponseCode(const string& buffer); void sendRequest(const string& buffer, const bool end = true); void readResponse(string& buffer, const bool multiLine, utility::progressionListener* progress = NULL); @@ -108,6 +123,10 @@ private: // POP3-specific options serviceInfos::property PROPERTY_OPTIONS_APOP; serviceInfos::property PROPERTY_OPTIONS_APOP_FALLBACK; +#if VMIME_HAVE_SASL_SUPPORT + serviceInfos::property PROPERTY_OPTIONS_SASL; + serviceInfos::property PROPERTY_OPTIONS_SASL_FALLBACK; +#endif // VMIME_HAVE_SASL_SUPPORT // Common properties serviceInfos::property PROPERTY_AUTH_USERNAME; diff --git a/vmime/net/sendmail/sendmailTransport.hpp b/vmime/net/sendmail/sendmailTransport.hpp index 292f5ebe..94ec4c15 100644 --- a/vmime/net/sendmail/sendmailTransport.hpp +++ b/vmime/net/sendmail/sendmailTransport.hpp @@ -43,7 +43,7 @@ class sendmailTransport : public transport { public: - sendmailTransport(ref sess, ref auth); + sendmailTransport(ref sess, ref auth); ~sendmailTransport(); const string getProtocolName() const; diff --git a/vmime/net/service.hpp b/vmime/net/service.hpp index cfb5c736..48dfdb71 100644 --- a/vmime/net/service.hpp +++ b/vmime/net/service.hpp @@ -24,7 +24,6 @@ #include "vmime/types.hpp" #include "vmime/net/session.hpp" -#include "vmime/net/authenticator.hpp" #include "vmime/net/serviceFactory.hpp" #include "vmime/net/serviceInfos.hpp" @@ -43,7 +42,7 @@ class service : public object { protected: - service(ref sess, const serviceInfos& infos, ref auth); + service(ref sess, const serviceInfos& infos, ref auth); public: @@ -110,13 +109,19 @@ public: * * @return authenticator object */ - ref getAuthenticator() const; + ref getAuthenticator() const; /** Return the authenticator object used with this service instance. * * @return authenticator object */ - ref getAuthenticator(); + ref getAuthenticator(); + + /** Set the authenticator object used with this service instance. + * + * @param auth authenticator object + */ + void setAuthenticator(ref auth); /** Set a property for this service (service prefix is added automatically). * @@ -150,7 +155,7 @@ public: private: ref m_session; - ref m_auth; + ref m_auth; }; diff --git a/vmime/net/serviceFactory.hpp b/vmime/net/serviceFactory.hpp index 2eb04e0e..209bdca5 100644 --- a/vmime/net/serviceFactory.hpp +++ b/vmime/net/serviceFactory.hpp @@ -30,9 +30,10 @@ #include "vmime/utility/url.hpp" #include "vmime/net/serviceInfos.hpp" -#include "vmime/net/authenticator.hpp" #include "vmime/net/timeoutHandler.hpp" +#include "vmime/security/authenticator.hpp" + #include "vmime/utility/progressionListener.hpp" @@ -69,7 +70,9 @@ public: public: - virtual ref create(ref sess, ref auth) const = 0; + virtual ref create + (ref sess, + ref auth) const = 0; virtual const string& getName() const = 0; virtual const serviceInfos& getInfos() const = 0; @@ -92,7 +95,9 @@ private: public: - ref create(ref sess, ref auth) const + ref create + (ref sess, + ref auth) const { return vmime::create (sess, auth); } @@ -137,7 +142,10 @@ public: * @throw exceptions::no_service_available if no service is registered * for this protocol */ - ref create(ref sess, const string& protocol, ref auth = NULL); + ref create + (ref sess, + const string& protocol, + ref auth = NULL); /** Create a new service instance from a URL. * @@ -149,7 +157,10 @@ public: * @throw exceptions::no_service_available if no service is registered * for this protocol */ - ref create(ref sess, const utility::url& u, ref auth = NULL); + ref create + (ref sess, + const utility::url& u, + ref auth = NULL); /** Return information about a registered protocol. * diff --git a/vmime/net/session.hpp b/vmime/net/session.hpp index c01ae5f2..e0d960b6 100644 --- a/vmime/net/session.hpp +++ b/vmime/net/session.hpp @@ -21,7 +21,7 @@ #define VMIME_NET_SESSION_HPP_INCLUDED -#include "vmime/net/authenticator.hpp" +#include "vmime/security/authenticator.hpp" #include "vmime/utility/url.hpp" @@ -60,7 +60,8 @@ public: * credentials by reading the session properties "auth.username" and "auth.password". * @return a new transport service */ - ref getTransport(ref auth = NULL); + ref getTransport + (ref auth = NULL); /** Return a transport service instance for the specified protocol. * @@ -70,7 +71,9 @@ public: * credentials by reading the session properties "auth.username" and "auth.password". * @return a new transport service */ - ref getTransport(const string& protocol, ref auth = NULL); + ref getTransport + (const string& protocol, + ref auth = NULL); /** Return a transport service instance for the specified URL. * @@ -80,7 +83,9 @@ public: * credentials by reading the session properties "auth.username" and "auth.password". * @return a new transport service */ - ref getTransport(const utility::url& url, ref auth = NULL); + ref getTransport + (const utility::url& url, + ref auth = NULL); /** Return a transport service instance for the protocol specified * in the session properties. @@ -92,7 +97,7 @@ public: * credentials by reading the session properties "auth.username" and "auth.password". * @return a new store service */ - ref getStore(ref auth = NULL); + ref getStore(ref auth = NULL); /** Return a store service instance for the specified protocol. * @@ -102,7 +107,9 @@ public: * credentials by reading the session properties "auth.username" and "auth.password". * @return a new store service */ - ref getStore(const string& protocol, ref auth = NULL); + ref getStore + (const string& protocol, + ref auth = NULL); /** Return a store service instance for the specified URL. * @@ -112,7 +119,9 @@ public: * credentials by reading the session properties "auth.username" and "auth.password". * @return a new store service */ - ref getStore(const utility::url& url, ref auth = NULL); + ref getStore + (const utility::url& url, + ref auth = NULL); /** Properties for the session and for the services. */ diff --git a/vmime/net/simpleAuthenticator.hpp b/vmime/net/simpleAuthenticator.hpp deleted file mode 100644 index 31a90a42..00000000 --- a/vmime/net/simpleAuthenticator.hpp +++ /dev/null @@ -1,62 +0,0 @@ -// -// VMime library (http://www.vmime.org) -// Copyright (C) 2002-2005 Vincent Richard -// -// 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. -// - -#ifndef VMIME_NET_SIMPLEAUTHENTICATOR_HPP_INCLUDED -#define VMIME_NET_SIMPLEAUTHENTICATOR_HPP_INCLUDED - - -#include "vmime/net/authenticator.hpp" - - -namespace vmime { -namespace net { - - -/** Basic implementation for an authenticator. - */ - -class simpleAuthenticator : public authenticator -{ -public: - - simpleAuthenticator(); - simpleAuthenticator(const string& username, const string& password); - -public: - - const string& getUsername() const; - void setUsername(const string& username); - - const string& getPassword() const; - void setPassword(const string& password); - - const authenticationInfos requestAuthInfos() const; - -private: - - string m_username; - string m_password; -}; - - -} // net -} // vmime - - -#endif // VMIME_NET_SIMPLEAUTHENTICATOR_HPP_INCLUDED diff --git a/vmime/net/smtp/SMTPTransport.hpp b/vmime/net/smtp/SMTPTransport.hpp index 1743ee7c..2ff67375 100644 --- a/vmime/net/smtp/SMTPTransport.hpp +++ b/vmime/net/smtp/SMTPTransport.hpp @@ -40,7 +40,7 @@ class SMTPTransport : public transport { public: - SMTPTransport(ref sess, ref auth); + SMTPTransport(ref sess, ref auth); ~SMTPTransport(); const string getProtocolName() const; @@ -58,18 +58,30 @@ public: private: - static const int responseCode(const string& response); - static const string responseText(const string& response); + static const int getResponseCode(const string& response); void sendRequest(const string& buffer, const bool end = true); - void readResponse(string& buffer); + const string readResponseLine(); + const int readResponse(string& text); + const int readAllResponses(string& text, const bool allText = false); void internalDisconnect(); + void authenticate(); +#if VMIME_HAVE_SASL_SUPPORT + void authenticateSASL(); +#endif // VMIME_HAVE_SASL_SUPPORT + + ref m_socket; bool m_authentified; + bool m_extendedSMTP; + string m_extendedSMTPResponse; + + string m_responseBuffer; + bool m_responseContinues; ref m_timeoutHandler; @@ -83,6 +95,10 @@ private: { // SMTP-specific options serviceInfos::property PROPERTY_OPTIONS_NEEDAUTH; +#if VMIME_HAVE_SASL_SUPPORT + serviceInfos::property PROPERTY_OPTIONS_SASL; + serviceInfos::property PROPERTY_OPTIONS_SASL_FALLBACK; +#endif // VMIME_HAVE_SASL_SUPPORT // Common properties serviceInfos::property PROPERTY_AUTH_USERNAME; diff --git a/vmime/net/store.hpp b/vmime/net/store.hpp index 5855c76d..997da468 100644 --- a/vmime/net/store.hpp +++ b/vmime/net/store.hpp @@ -37,7 +37,7 @@ class store : public service { protected: - store(ref sess, const serviceInfos& infos, ref auth) + store(ref sess, const serviceInfos& infos, ref auth) : service(sess, infos, auth) { } public: diff --git a/vmime/net/transport.hpp b/vmime/net/transport.hpp index f4be446d..11950034 100644 --- a/vmime/net/transport.hpp +++ b/vmime/net/transport.hpp @@ -42,7 +42,7 @@ class transport : public service { protected: - transport(ref sess, const serviceInfos& infos, ref auth); + transport(ref sess, const serviceInfos& infos, ref auth); public: diff --git a/vmime/security/authenticator.hpp b/vmime/security/authenticator.hpp new file mode 100644 index 00000000..04522b10 --- /dev/null +++ b/vmime/security/authenticator.hpp @@ -0,0 +1,116 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// 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. +// + +#ifndef VMIME_SECURITY_AUTHENTICATOR_HPP_INCLUDED +#define VMIME_SECURITY_AUTHENTICATOR_HPP_INCLUDED + + +#include "vmime/types.hpp" + + +// Forward declarations +namespace vmime { +namespace net { + +class service; + +} // net +} // vmime + + +namespace vmime { +namespace security { + + +/** Provides required information for user authentication. The same + * information can be requested multiple time (eg. in IMAP, there is a + * new connection started each time a folder is open), so the object is + * responsible for caching the information to avoid useless interactions + * with the user. + * + * Usually, you should not inherit from this class, but instead from the + * more convenient defaultAuthenticator class. + * + * WARNING: an authenticator should be used with one and ONLY ONE messaging + * service at a time. + */ +class authenticator : public object +{ +public: + + /** Return the authentication identity (usually, this + * is the username). + * + * @return username + * @throw exceptions::no_auth_information if the information + * could not be provided + */ + virtual const string getUsername() const = 0; + + /** Return the password of the authentication identity. + * + * @return password + * @throw exceptions::no_auth_information if the information + * could not be provided + */ + virtual const string getPassword() const = 0; + + /** Return the local host name of the machine. + * + * @return hostname + * @throw exceptions::no_auth_information if the information + * could not be provided + */ + virtual const string getHostname() const = 0; + + /** Return the anonymous token (usually, this is the user's + * email address). + * + * @return anonymous token + * @throw exceptions::no_auth_information if the information + * could not be provided + */ + virtual const string getAnonymousToken() const = 0; + + /** Return the registered service name of the application + * service (eg: "imap"). This can be used by GSSAPI or DIGEST-MD5 + * mechanisms with SASL. + * + * @return service name + * @throw exceptions::no_auth_information if the information + * could not be provided + */ + virtual const string getServiceName() const = 0; + + /** Called by the messaging service to allow this authenticator to + * know which service is currently using it. This is called just + * before the service starts the authentication process. + * + * @param serv messaging service instance + */ + virtual void setService(ref serv) = 0; +}; + + +} // security +} // vmime + + +#endif // VMIME_SECURITY_AUTHENTICATOR_HPP_INCLUDED + diff --git a/vmime/net/defaultAuthenticator.hpp b/vmime/security/defaultAuthenticator.hpp similarity index 56% rename from vmime/net/defaultAuthenticator.hpp rename to vmime/security/defaultAuthenticator.hpp index a9e58e94..73f65cdc 100644 --- a/vmime/net/defaultAuthenticator.hpp +++ b/vmime/security/defaultAuthenticator.hpp @@ -17,44 +17,45 @@ // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // -#ifndef VMIME_NET_DEFAULTAUTHENTICATOR_HPP_INCLUDED -#define VMIME_NET_DEFAULTAUTHENTICATOR_HPP_INCLUDED +#ifndef VMIME_SECURITY_DEFAULTAUTHENTICATOR_HPP_INCLUDED +#define VMIME_SECURITY_DEFAULTAUTHENTICATOR_HPP_INCLUDED -#include "vmime/net/authenticator.hpp" -#include "vmime/propertySet.hpp" +#include "vmime/security/authenticator.hpp" namespace vmime { -namespace net { +namespace security { -class session; - - -/** Default implementation for authenticator. It simply returns - * the credentials set in the session properties (named 'username' - * and 'password'). This is the default implementation used if - * you do not write your own authenticator object. +/** An authenticator that can provide some basic information by + * reading in the messaging session properties. */ - class defaultAuthenticator : public authenticator { public: - defaultAuthenticator(weak_ref session, const string& prefix); + defaultAuthenticator(); + ~defaultAuthenticator(); - const authenticationInfos requestAuthInfos() const; + const string getUsername() const; + const string getPassword() const; + const string getHostname() const; + const string getAnonymousToken() const; + const string getServiceName() const; + + void setService(ref serv); + weak_ref getService() const; private: - weak_ref m_session; - const string m_prefix; + weak_ref m_service; }; -} // net +} // security } // vmime -#endif // VMIME_NET_DEFAULTAUTHENTICATOR_HPP_INCLUDED +#endif // VMIME_SECURITY_DEFAULTAUTHENTICATOR_HPP_INCLUDED + diff --git a/vmime/security/sasl/SASLAuthenticator.hpp b/vmime/security/sasl/SASLAuthenticator.hpp new file mode 100644 index 00000000..fc833f36 --- /dev/null +++ b/vmime/security/sasl/SASLAuthenticator.hpp @@ -0,0 +1,84 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// 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. +// + +#ifndef VMIME_SECURITY_SASL_SASLAUTHENTICATOR_HPP_INCLUDED +#define VMIME_SECURITY_SASL_SASLAUTHENTICATOR_HPP_INCLUDED + + +#include "vmime/types.hpp" + +#include "vmime/security/authenticator.hpp" + + +namespace vmime { +namespace security { +namespace sasl { + + +class SASLMechanism; +class SASLSession; + + +/** SASL-aware authenticator. + * + * Usually, you should not inherit from this class, but instead from the + * more convenient defaultSASLAuthenticator class. + */ +class SASLAuthenticator : public authenticator +{ +public: + + /** This method is called to allow the client to choose the + * authentication mechanisms that will be used. By default, + * the more secure mechanisms are chosen. + * + * @param available available mechanisms + * @param suggested suggested mechanism (or NULL if the system + * could not suggest a mechanism) + * @return ordered list of mechanism to use among the available + * mechanisms (from the first to try to the last) + */ + virtual const std::vector > getAcceptableMechanisms + (const std::vector >& available, + ref suggested) const = 0; + + /** Set the SASL session which is using this authenticator. + * + * @param sess SASL session + */ + virtual void setSASLSession(ref sess) = 0; + + /** Set the SASL mechanism which has been selected for the + * SASL authentication process. This may be called several times + * if the multiple mechanisms are tried by the service which + * use this authentication. + * + * @param mech SASL mechanism + */ + virtual void setSASLMechanism(ref mech) = 0; +}; + + +} // sasl +} // security +} // vmime + + +#endif // VMIME_SECURITY_SASL_SASLAUTHENTICATOR_HPP_INCLUDED + diff --git a/vmime/security/sasl/SASLContext.hpp b/vmime/security/sasl/SASLContext.hpp new file mode 100644 index 00000000..1a71ed76 --- /dev/null +++ b/vmime/security/sasl/SASLContext.hpp @@ -0,0 +1,116 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// 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. +// + +#ifndef VMIME_SECURITY_SASL_SASLCONTEXT_HPP_INCLUDED +#define VMIME_SECURITY_SASL_SASLCONTEXT_HPP_INCLUDED + + +#include "vmime/types.hpp" + +#include "vmime/security/sasl/SASLSession.hpp" +#include "vmime/security/sasl/SASLMechanismFactory.hpp" + + +namespace vmime { +namespace security { +namespace sasl { + + +/** An SASL client context. + */ +class SASLContext : public object +{ + friend class SASLSession; + friend class builtinSASLMechanism; + +public: + + ~SASLContext(); + + /** Construct and initialize a new SASL context. + */ + SASLContext(); + + /** Create and initialize a new SASL session. + * + * @param serviceName name of the service which will use the session + * @param auth authenticator object to use during the session + * @param mech SASL mechanism + * @return a new SASL session + */ + ref createSession + (const string& serviceName, + ref auth, ref mech); + + /** Create an instance of an SASL mechanism. + * + * @param name mechanism name + * @return a new instance of the specified SASL mechanism + * @throw exceptions::no_such_mechanism if no mechanism is + * registered for the specified name + */ + ref createMechanism(const string& name); + + /** Suggests an SASL mechanism among a set of mechanisms + * supported by the server. + * + * @param mechs list of mechanisms + * @return suggested mechanism (usually the safest mechanism + * supported by both the client and the server) + */ + ref suggestMechanism + (const std::vector >& mechs); + + /** Helper function for decoding Base64-encoded challenge. + * + * @param input input buffer + * @param output output buffer + * @param outputLen length of output buffer + */ + void decodeB64(const string& input, byte** output, int* outputLen); + + /** Helper function for encoding challenge in Base64. + * + * @param input input buffer + * @param inputLen length of input buffer + * @return Base64-encoded challenge + */ + const string encodeB64(const byte* input, const int inputLen); + +private: + + static const string getErrorMessage(const string& fname, const int code); + + +#ifdef GSASL_VERSION + Gsasl* m_gsaslContext; +#else + void* m_gsaslContext; +#endif // GSASL_VERSION + +}; + + +} // sasl +} // security +} // vmime + + +#endif // VMIME_SECURITY_SASL_SASLCONTEXT_HPP_INCLUDED + diff --git a/vmime/security/sasl/SASLMechanism.hpp b/vmime/security/sasl/SASLMechanism.hpp new file mode 100644 index 00000000..366198e7 --- /dev/null +++ b/vmime/security/sasl/SASLMechanism.hpp @@ -0,0 +1,119 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// 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. +// + +#ifndef VMIME_SECURITY_SASL_SASLMECHANISM_HPP_INCLUDED +#define VMIME_SECURITY_SASL_SASLMECHANISM_HPP_INCLUDED + + +#include "vmime/types.hpp" + + +namespace vmime { +namespace security { +namespace sasl { + + +class SASLSession; + + +/** An SASL mechanism. + */ +class SASLMechanism : public object +{ +public: + + /** Return the name of this mechanism. + * + * @return mechanism name + */ + virtual const string getName() const = 0; + + /** Perform one step of SASL authentication. Accept data from the + * server (challenge), process it and return data to be returned + * in response to the server. + * + * @param sess SASL session + * @param challenge challenge sent from the server + * @param challengeLen length of challenge + * @param response response to send to the server (allocated by + * this function, free with delete[]) + * @param responseLen length of response buffer + * @return true if authentication terminated successfully, or + * false if the authentication process should continue + * @throw exceptions::sasl_exception if an error occured during + * authentication (in this case, the values in 'response' and + * 'responseLen' are undetermined) + */ + virtual const bool step + (ref sess, + const byte* challenge, const int challengeLen, + byte** response, int* responseLen) = 0; + + /** Check whether authentication has completed. If false, more + * calls to evaluateChallenge() are needed to complete the + * authentication process). + * + * @return true if the authentication has finished, or false + * otherwise + */ + virtual const bool isComplete() const = 0; + + /** Encode data according to negotiated SASL mechanism. This + * might mean that data is integrity or privacy protected. + * + * @param sess SASL session + * @param input input buffer + * @param inputLen length of input buffer + * @param output output buffer (allocated bu the function, + * free with delete[]) + * @param outputLen length of output buffer + * @throw exceptions::sasl_exception if an error occured during + * the encoding of data (in this case, the values in 'output' and + * 'outputLen' are undetermined) + */ + virtual void encode(ref sess, + const byte* input, const int inputLen, + byte** output, int* outputLen) = 0; + + /** Decode data according to negotiated SASL mechanism. This + * might mean that data is integrity or privacy protected. + * + * @param sess SASL session + * @param input input buffer + * @param inputLen length of input buffer + * @param output output buffer (allocated bu the function, + * free with delete[]) + * @param outputLen length of output buffer + * @throw exceptions::sasl_exception if an error occured during + * the encoding of data (in this case, the values in 'output' and + * 'outputLen' are undetermined) + */ + virtual void decode(ref sess, + const byte* input, const int inputLen, + byte** output, int* outputLen) = 0; +}; + + +} // sasl +} // security +} // vmime + + +#endif // VMIME_SECURITY_SASL_SASLMECHANISM_HPP_INCLUDED + diff --git a/vmime/security/sasl/SASLMechanismFactory.hpp b/vmime/security/sasl/SASLMechanismFactory.hpp new file mode 100644 index 00000000..5cf0b3c7 --- /dev/null +++ b/vmime/security/sasl/SASLMechanismFactory.hpp @@ -0,0 +1,131 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// 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. +// + +#ifndef VMIME_SECURITY_SASL_SASLMECHANISMFACTORY_HPP_INCLUDED +#define VMIME_SECURITY_SASL_SASLMECHANISMFACTORY_HPP_INCLUDED + + +#include "vmime/types.hpp" +#include "vmime/base.hpp" + +#include "vmime/security/sasl/SASLMechanism.hpp" + +#include + + +namespace vmime { +namespace security { +namespace sasl { + + +class SASLContext; + + +/** Constructs SASL mechanism objects. + */ +class SASLMechanismFactory : public object +{ +private: + + SASLMechanismFactory(); + ~SASLMechanismFactory(); + + + class registeredMechanism : public object + { + public: + + virtual ref create + (ref ctx, const string& name) = 0; + }; + + template + class registeredMechanismImpl : public registeredMechanism + { + public: + + ref create(ref ctx, const string& name) + { + return vmime::create (ctx, name); + } + }; + + typedef std::map > MapType; + MapType m_mechs; + +public: + + static SASLMechanismFactory* getInstance(); + + /** Register a mechanism into this factory, so that subsequent + * calls to create return a valid object for this mechanism. + * + * @param name mechanism name + */ + template + void registerMechanism(const string& name) + { + m_mechs.insert(MapType::value_type(name, + vmime::create >())); + } + + /** Create a mechanism object given its name. + * + * @param ctx SASL context + * @param name mechanism name + * @return a new mechanism object + * @throw exceptions::no_such_mechanism if no mechanism is + * registered for the specified name + */ + ref create(ref ctx, const string& name); + + /** Return a list of supported mechanisms. This includes mechanisms + * registered using registerMechanism() as well as the ones that + * are built-in. + * + * @return list of supported mechanisms + */ + const std::vector getSupportedMechanisms() const; + + /** Test whether an authentication mechanism is supported. + * + * @param name mechanism name + * @return true if the specified mechanism is supported, + * false otherwise + */ + const bool isMechanismSupported(const string& name) const; + +private: + +#ifdef GSASL_VERSION + Gsasl* m_gsaslContext; +#else + void* m_gsaslContext; +#endif // GSASL_VERSION + +}; + + +} // sasl +} // security +} // vmime + + +#endif // VMIME_SECURITY_SASL_SASLMECHANISMFACTORY_HPP_INCLUDED + diff --git a/vmime/security/sasl/SASLSession.hpp b/vmime/security/sasl/SASLSession.hpp new file mode 100644 index 00000000..0a76caa3 --- /dev/null +++ b/vmime/security/sasl/SASLSession.hpp @@ -0,0 +1,150 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// 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. +// + +#ifndef VMIME_SECURITY_SASL_SASLSESSION_HPP_INCLUDED +#define VMIME_SECURITY_SASL_SASLSESSION_HPP_INCLUDED + + +#include "vmime/types.hpp" + +#include "vmime/security/sasl/SASLAuthenticator.hpp" +#include "vmime/security/sasl/SASLMechanism.hpp" +#include "vmime/security/sasl/SASLSocket.hpp" + + +namespace vmime { +namespace security { +namespace sasl { + + +class SASLContext; + + +/** An SASL client session. + */ +class SASLSession : public object +{ + friend class builtinSASLMechanism; + friend class SASLSocket; + +public: + + ~SASLSession(); + + /** Construct a new SASL session. + * + * @param serviceName name of the service using this session + * @param ctx SASL context + * @param auth authenticator to use for this session + * @param mech SASL mechanism + */ + SASLSession(const string& serviceName, ref ctx, + ref auth, ref mech); + + /** Initialize this SASL session. This must be called before + * calling any other method on this object (except accessors). + */ + void init(); + + /** Return the authenticator used for this session. This is the + * authenticator which has been previously set with a call to + * setAuthenticator(). + * + * @return authenticator object + */ + ref getAuthenticator(); + + /** Return the mechanism used for this session. + * + * @return SASL mechanism + */ + ref getMechanism(); + + /** Return the SASL context. + * + * @return SASL context + */ + ref getContext(); + + /** Perform one step of SASL authentication. Accept data from the + * server (challenge), process it and return data to be returned + * in response to the server. + * + * @param challenge challenge sent from the server + * @param challengeLen length of challenge + * @param response response to send to the server (allocated by + * this function, free with delete[]) + * @param responseLen length of response buffer + * @return true if authentication terminated successfully, or + * false if the authentication process should continue + * @throw exceptions::sasl_exception if an error occured during + * authentication (in this case, the values in 'response' and + * 'responseLen' are undetermined) + */ + const bool evaluateChallenge + (const byte* challenge, const int challengeLen, + byte** response, int* responseLen); + + /** Return a socket in which transmitted data is integrity + * and/or privacy protected, depending on the QOP (Quality of + * Protection) negotiated during the SASL authentication. + * + * @param sok socket to wrap + * @return secured socket + */ + ref getSecuredSocket(ref sok); + + /** Return the name of the service which is using this + * SASL session (eg. "imap"). This value should be returned + * by the authenticator when INFO_SERVICE is requested. + * + * @return service name + */ + const string getServiceName() const; + +private: + + const string m_serviceName; + + ref m_context; + ref m_auth; + ref m_mech; + +#ifdef GSASL_VERSION + Gsasl* m_gsaslContext; + Gsasl_session* m_gsaslSession; + + static int gsaslCallback(Gsasl* ctx, Gsasl_session* sctx, Gsasl_property prop); +#else + void* m_gsaslContext; + void* m_gsaslSession; + + static int gsaslCallback(void* ctx, void* sctx, int prop); +#endif // GSASL_VERSION + +}; + + +} // sasl +} // security +} // vmime + + +#endif // VMIME_SECURITY_SASL_SASLSESSION_HPP_INCLUDED + diff --git a/vmime/net/authenticationInfos.hpp b/vmime/security/sasl/SASLSocket.hpp similarity index 50% rename from vmime/net/authenticationInfos.hpp rename to vmime/security/sasl/SASLSocket.hpp index 2bc394fd..e29415cd 100644 --- a/vmime/net/authenticationInfos.hpp +++ b/vmime/security/sasl/SASLSocket.hpp @@ -17,48 +17,60 @@ // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // -#ifndef VMIME_NET_AUTHENTICATIONINFOS_HPP_INCLUDED -#define VMIME_NET_AUTHENTICATIONINFOS_HPP_INCLUDED +#ifndef VMIME_SECURITY_SASL_SASLSOCKET_HPP_INCLUDED +#define VMIME_SECURITY_SASL_SASLSOCKET_HPP_INCLUDED #include "vmime/types.hpp" +#include "vmime/net/socket.hpp" + namespace vmime { -namespace net { +namespace security { +namespace sasl { -/** This class encapsulates user credentials. +class SASLSession; + + +/** A socket which provides data integrity and/or privacy protection. */ - -class authenticationInfos : public object +class SASLSocket : public net::socket { public: - authenticationInfos(const string& username, const string& password); - authenticationInfos(const authenticationInfos& infos); + SASLSocket(ref sess, ref wrapped); + ~SASLSocket(); - /** Return the user account name. - * - * @return account name - */ - const string& getUsername() const; + void connect(const string& address, const port_t port); + void disconnect(); - /** Return the user account password. - * - * @return account password - */ - const string& getPassword() const; + const bool isConnected() const; + + void receive(string& buffer); + const int receiveRaw(char* buffer, const int count); + + void send(const string& buffer); + void sendRaw(const char* buffer, const int count); private: - string m_username; - string m_password; + ref m_session; + ref m_wrapped; + + byte* m_pendingBuffer; + int m_pendingPos; + int m_pendingLen; + + char m_recvBuffer[65536]; }; -} // net +} // sasl +} // security } // vmime -#endif // VMIME_NET_AUTHENTICATIONINFOS_HPP_INCLUDED +#endif // VMIME_SECURITY_SASL_SASLSOCKET_HPP_INCLUDED + diff --git a/vmime/security/sasl/builtinSASLMechanism.hpp b/vmime/security/sasl/builtinSASLMechanism.hpp new file mode 100644 index 00000000..4a26a922 --- /dev/null +++ b/vmime/security/sasl/builtinSASLMechanism.hpp @@ -0,0 +1,82 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// 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. +// + +#ifndef VMIME_SECURITY_SASL_BUILTINSASLMECHANISM_HPP_INCLUDED +#define VMIME_SECURITY_SASL_BUILTINSASLMECHANISM_HPP_INCLUDED + + +#include "vmime/security/sasl/SASLMechanism.hpp" + + +namespace vmime { +namespace security { +namespace sasl { + + +class SASLContext; + + +/** A built-in authentication mechanism that relies on + * the GNU SASL library. + */ +class builtinSASLMechanism : public SASLMechanism +{ +public: + + builtinSASLMechanism(ref ctx, const string& name); + ~builtinSASLMechanism(); + + + const string getName() const; + + const bool step + (ref sess, + const byte* challenge, const int challengeLen, + byte** response, int* responseLen); + + const bool isComplete() const; + + void encode(ref sess, + const byte* input, const int inputLen, + byte** output, int* outputLen); + + void decode(ref sess, + const byte* input, const int inputLen, + byte** output, int* outputLen); + +private: + + /** SASL context */ + ref m_context; + + /** Mechanism name */ + const string m_name; + + /** Authentication process status. */ + bool m_complete; +}; + + +} // sasl +} // security +} // vmime + + +#endif // VMIME_SECURITY_SASL_BUILTINSASLMECHANISM_HPP_INCLUDED + diff --git a/vmime/security/sasl/defaultSASLAuthenticator.hpp b/vmime/security/sasl/defaultSASLAuthenticator.hpp new file mode 100644 index 00000000..9011b779 --- /dev/null +++ b/vmime/security/sasl/defaultSASLAuthenticator.hpp @@ -0,0 +1,80 @@ +// +// VMime library (http://www.vmime.org) +// Copyright (C) 2002-2005 Vincent Richard +// +// 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. +// + +#ifndef VMIME_SECURITY_SASL_DEFAULTSASLAUTHENTICATOR_HPP_INCLUDED +#define VMIME_SECURITY_SASL_DEFAULTSASLAUTHENTICATOR_HPP_INCLUDED + + +#include "vmime/security/sasl/SASLAuthenticator.hpp" +#include "vmime/security/defaultAuthenticator.hpp" + + +namespace vmime { +namespace security { +namespace sasl { + + +/** An authenticator that is capable of providing information + * for simple authentication mechanisms (username and password). + */ +class defaultSASLAuthenticator : public SASLAuthenticator +{ +public: + + defaultSASLAuthenticator(); + ~defaultSASLAuthenticator(); + + const std::vector > getAcceptableMechanisms + (const std::vector >& available, + ref suggested) const; + + const string getUsername() const; + const string getPassword() const; + const string getHostname() const; + const string getAnonymousToken() const; + const string getServiceName() const; + + void setService(ref serv); + weak_ref getService() const; + + void setSASLSession(ref sess); + ref getSASLSession() const; + + void setSASLMechanism(ref mech); + ref getSASLMechanism() const; + +private: + + defaultAuthenticator m_default; + + + weak_ref m_service; + + ref m_saslSession; + ref m_saslMech; +}; + + +} // sasl +} // security +} // vmime + + +#endif // VMIME_SECURITY_SASL_DEFAULTSASLAUTHENTICATOR_HPP_INCLUDED + diff --git a/vmime/types.hpp b/vmime/types.hpp index 4e2fa923..47cc8507 100644 --- a/vmime/types.hpp +++ b/vmime/types.hpp @@ -23,6 +23,7 @@ #include #include +#include #include "vmime/config.hpp" #include "vmime/utility/smartPtr.hpp" @@ -40,6 +41,7 @@ namespace vmime typedef int char_t; typedef vmime_uint8 byte; + typedef std::vector byteArray; // Some aliases namespace utils = utility; diff --git a/vmime/utility/stream.hpp b/vmime/utility/stream.hpp index 4b1cba2d..754f65a3 100644 --- a/vmime/utility/stream.hpp +++ b/vmime/utility/stream.hpp @@ -326,6 +326,29 @@ private: }; +/** An adapter class for reading from an array of bytes. + */ + +class inputStreamByteBufferAdapter : public inputStream +{ +public: + + inputStreamByteBufferAdapter(const byte* buffer, size_type length); + + const bool eof() const; + void reset(); + const size_type read(value_type* const data, const size_type count); + const size_type skip(const size_type count); + +private: + + const byte* m_buffer; + const size_type m_length; + + size_type m_pos; +}; + + #if VMIME_HAVE_MESSAGING_FEATURES diff --git a/vmime/vmime.hpp b/vmime/vmime.hpp index 3a6b6474..ae44d963 100644 --- a/vmime/vmime.hpp +++ b/vmime/vmime.hpp @@ -86,6 +86,21 @@ #include "vmime/utility/datetimeUtils.hpp" #include "vmime/utility/filteredStream.hpp" +// Security +#include "vmime/security/authenticator.hpp" +#include "vmime/security/defaultAuthenticator.hpp" + +// Security/digest +#include "vmime/security/digest/messageDigestFactory.hpp" + +// Security/SASL +#if VMIME_HAVE_SASL_SUPPORT + #include "vmime/security/sasl/SASLAuthenticator.hpp" + #include "vmime/security/sasl/defaultSASLAuthenticator.hpp" + #include "vmime/security/sasl/SASLContext.hpp" + #include "vmime/security/sasl/SASLSession.hpp" +#endif // VMIME_HAVE_SASL_SUPPORT + // Messaging features #if VMIME_HAVE_MESSAGING_FEATURES #include "vmime/net/socket.hpp" @@ -95,9 +110,6 @@ #include "vmime/net/transport.hpp" #include "vmime/net/session.hpp" - #include "vmime/net/authenticator.hpp" - #include "vmime/net/defaultAuthenticator.hpp" - #include "vmime/net/simpleAuthenticator.hpp" #include "vmime/net/folder.hpp" #include "vmime/net/message.hpp"