diff options
author | Andreas Nilsson <agralius@gmail.com> | 2014-08-29 13:10:29 -0400 |
---|---|---|
committer | Andreas Nilsson <agralius@gmail.com> | 2014-09-04 14:54:11 -0400 |
commit | c14c1f113d56da4e32ac13a123028c916f0c8cff (patch) | |
tree | 28407824a59d40567902039ce5c14cdde1aa0d7a /src | |
parent | 86f017917aac53e1adfe991fc21a08416c84d990 (diff) | |
download | mongo-c14c1f113d56da4e32ac13a123028c916f0c8cff.tar.gz |
SERVER-7596 SASL client code refactor for SCRAM
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/SConscript | 11 | ||||
-rw-r--r-- | src/mongo/client/cyrus_sasl_client_session.cpp | 314 | ||||
-rw-r--r-- | src/mongo/client/cyrus_sasl_client_session.h | 85 | ||||
-rw-r--r-- | src/mongo/client/native_sasl_client_session.cpp | 66 | ||||
-rw-r--r-- | src/mongo/client/native_sasl_client_session.h | 57 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate_impl.cpp | 11 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_session.cpp | 290 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_session.h | 46 | ||||
-rw-r--r-- | src/mongo/client/sasl_sspi.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_authentication_session.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_authentication_session.h | 4 |
11 files changed, 567 insertions, 326 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript index c14c58d7eee..4e6d5e1642a 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -286,8 +286,11 @@ env.Library('clientdriver', [ "client/dbclient.cpp", "client/dbclient_rs.cpp", "client/dbclientcursor.cpp", + 'client/native_sasl_client_session.cpp', "client/replica_set_monitor.cpp", 'client/sasl_client_authenticate.cpp', + "client/sasl_client_authenticate_impl.cpp", + 'client/sasl_client_session.cpp', "client/syncclusterconnection.cpp", "db/dbmessage.cpp" ], @@ -336,16 +339,16 @@ if env['MONGO_BUILD_SASL_CLIENT']: if env['PYSYSPLATFORM'] == "win32": saslLibs.extend(["secur32"]) - env.Library('sasl_client_session', - ['client/sasl_client_session.cpp', + env.Library('cyrus_sasl_client_session', + ['client/cyrus_sasl_client_session.cpp', 'client/sasl_sspi.cpp'], LIBDEPS = [ + 'clientdriver', 'foundation', 'signal_handlers_synchronous', ], SYSLIBDEPS=saslLibs) - commonFiles.extend(['client/sasl_client_authenticate_impl.cpp']) - extraCommonLibdeps.append('sasl_client_session') + extraCommonLibdeps.append('cyrus_sasl_client_session') # handle processinfo* processInfoFiles = [ "util/processinfo.cpp" ] diff --git a/src/mongo/client/cyrus_sasl_client_session.cpp b/src/mongo/client/cyrus_sasl_client_session.cpp new file mode 100644 index 00000000000..1c7d92c1924 --- /dev/null +++ b/src/mongo/client/cyrus_sasl_client_session.cpp @@ -0,0 +1,314 @@ +/* Copyright 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/client/cyrus_sasl_client_session.h" + +#include "mongo/base/init.h" +#include "mongo/util/allocator.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/concurrency/mutex.h" +#include "mongo/util/mongoutils/str.h" +#include "mongo/util/signal_handlers_synchronous.h" + +namespace mongo { +namespace { + + SaslClientSession* createCyrusSaslClientSession() { + return new CyrusSaslClientSession(); + } + + /* + * Allocator functions to be used by the SASL library, if the client + * doesn't initialize the library for us. + */ + +// Version 2.1.26 is the first version to use size_t in the allocator signatures +#if (SASL_VERSION_FULL >= ((2 << 16) | (1 << 8) | 26)) + typedef size_t SaslAllocSize; +#else + typedef unsigned long SaslAllocSize; +#endif + + typedef int(*SaslCallbackFn)(); + + void* saslOurMalloc(SaslAllocSize sz) { + return mongoMalloc(sz); + } + + void* saslOurCalloc(SaslAllocSize count, SaslAllocSize size) { + void* ptr = calloc(count, size); + if (!ptr) { + reportOutOfMemoryErrorAndExit(); + } + return ptr; + } + + void* saslOurRealloc(void* ptr, SaslAllocSize sz) { + return mongoRealloc(ptr, sz); + } + + /* + * Mutex functions to be used by the SASL library, if the client doesn't initialize the library + * for us. + */ + + void* saslMutexAlloc(void) { + return new SimpleMutex("sasl"); + } + + int saslMutexLock(void* mutex) { + static_cast<SimpleMutex*>(mutex)->lock(); + return SASL_OK; + } + + int saslMutexUnlock(void* mutex) { + static_cast<SimpleMutex*>(mutex)->unlock(); + return SASL_OK; + } + + void saslMutexFree(void* mutex) { + delete static_cast<SimpleMutex*>(mutex); + } + + /** + * Configures the SASL library to use allocator and mutex functions we specify, + * unless the client application has previously initialized the SASL library. + */ + MONGO_INITIALIZER(CyrusSaslAllocatorsAndMutexes)(InitializerContext*) { + sasl_set_alloc(saslOurMalloc, + saslOurCalloc, + saslOurRealloc, + free); + + sasl_set_mutex(saslMutexAlloc, + saslMutexLock, + saslMutexUnlock, + saslMutexFree); + return Status::OK(); + } + + int saslClientLogSwallow(void *context, int priority, const char *message) { + return SASL_OK; // do nothing + } + + /** + * Initializes the client half of the SASL library, but is effectively a no-op if the client + * application has already done it. + * + * If a client wishes to override this initialization but keep the allocator and mutex + * initialization, it should implement a MONGO_INITIALIZER_GENERAL with + * CyrusSaslAllocatorsAndMutexes as a prerequisite and CyrusSaslClientContext as a + * dependent. If it wishes to override both, it should implement a MONGO_INITIALIZER_GENERAL + * with CyrusSaslAllocatorsAndMutexes and CyrusSaslClientContext as dependents, or + * initialize the library before calling mongo::runGlobalInitializersOrDie(). + */ + MONGO_INITIALIZER_WITH_PREREQUISITES(CyrusSaslClientContext, + ("NativeSaslClientContext", + "CyrusSaslAllocatorsAndMutexes")) + (InitializerContext* context) { + + static sasl_callback_t saslClientGlobalCallbacks[] = + { { SASL_CB_LOG, SaslCallbackFn(saslClientLogSwallow), NULL /* context */ }, + { SASL_CB_LIST_END } }; + + // If the client application has previously called sasl_client_init(), the callbacks passed + // in here are ignored. + // + // TODO: Call sasl_client_done() at shutdown when we have a story for orderly shutdown. + int result = sasl_client_init(saslClientGlobalCallbacks); + if (result != SASL_OK) { + return Status(ErrorCodes::UnknownError, + mongoutils::str::stream() << + "Could not initialize sasl client components (" << + sasl_errstring(result, NULL, NULL) << + ")"); + } + + SaslClientSession::create = createCyrusSaslClientSession; + return Status::OK(); + } + + /** + * Callback registered on the sasl_conn_t underlying a CyrusSaslClientSession to allow the Cyrus SASL + * library to query for the authentication id and other simple string configuration parameters. + * + * Note that in Mongo, the authentication and authorization ids (authid and authzid) are always + * the same. These correspond to SASL_CB_AUTHNAME and SASL_CB_USER. + */ + int saslClientGetSimple(void* context, + int id, + const char** result, + unsigned* resultLen) throw () { + CyrusSaslClientSession* session = static_cast<CyrusSaslClientSession*>(context); + if (!session || !result) + return SASL_BADPARAM; + + CyrusSaslClientSession::Parameter requiredParameterId; + switch (id) { + case SASL_CB_AUTHNAME: + case SASL_CB_USER: + requiredParameterId = CyrusSaslClientSession::parameterUser; + break; + default: + return SASL_FAIL; + } + + if (!session->hasParameter(requiredParameterId)) + return SASL_FAIL; + StringData value = session->getParameter(requiredParameterId); + *result = value.rawData(); + if (resultLen) + *resultLen = static_cast<unsigned>(value.size()); + return SASL_OK; + } + + /** + * Callback registered on the sasl_conn_t underlying a CyrusSaslClientSession to allow + * the Cyrus SASL library to query for the password data. + */ + int saslClientGetPassword(sasl_conn_t* conn, + void* context, + int id, + sasl_secret_t** outSecret) throw () { + + CyrusSaslClientSession* session = static_cast<CyrusSaslClientSession*>(context); + if (!session || !outSecret) + return SASL_BADPARAM; + + sasl_secret_t* secret = session->getPasswordAsSecret(); + if (secret == NULL) { + sasl_seterror(conn, 0, "No password data provided"); + return SASL_FAIL; + } + + *outSecret = secret; + return SASL_OK; + } +} // namespace + + CyrusSaslClientSession::CyrusSaslClientSession() : + SaslClientSession(), + _saslConnection(NULL), + _step(0), + _done(false) { + + const sasl_callback_t callbackTemplate[maxCallbacks] = { + { SASL_CB_AUTHNAME, SaslCallbackFn(saslClientGetSimple), this }, + { SASL_CB_USER, SaslCallbackFn(saslClientGetSimple), this }, + { SASL_CB_PASS, SaslCallbackFn(saslClientGetPassword), this }, + { SASL_CB_LIST_END } + }; + std::copy(callbackTemplate, callbackTemplate + maxCallbacks, _callbacks); + } + + CyrusSaslClientSession::~CyrusSaslClientSession() { + sasl_dispose(&_saslConnection); + } + + void CyrusSaslClientSession::setParameter(Parameter id, const StringData& value) { + fassert(18665, id >= 0 && id < numParameters); + if (id == parameterPassword) { + // The parameterPassword is stored as a sasl_secret_t, while other + // parameters are stored directly. This facilitates memory ownership management for + // getPasswordAsSecret(). + _secret.reset(new char[sizeof(sasl_secret_t) + value.size() + 1]); + sasl_secret_t* secret = + static_cast<sasl_secret_t*>(static_cast<void*>(_secret.get())); + secret->len = value.size(); + value.copyTo(static_cast<char*>(static_cast<void*>(&secret->data[0])), false); + } + SaslClientSession::setParameter(id, value); + } + + sasl_secret_t* CyrusSaslClientSession::getPasswordAsSecret() { + // See comment in setParameter() about the special storage of parameterPassword. + return static_cast<sasl_secret_t*>( + static_cast<void*>(_secret.get())); + } + + Status CyrusSaslClientSession::initialize() { + if (_saslConnection != NULL) + return Status(ErrorCodes::AlreadyInitialized, + "Cannot reinitialize CyrusSaslClientSession."); + + int result = sasl_client_new(getParameter(parameterServiceName).toString().c_str(), + getParameter(parameterServiceHostname).toString().c_str(), + NULL, + NULL, + _callbacks, + 0, + &_saslConnection); + + if (SASL_OK != result) { + return Status(ErrorCodes::UnknownError, + mongoutils::str::stream() << sasl_errstring(result, NULL, NULL)); + } + + return Status::OK(); + } + + Status CyrusSaslClientSession::step(const StringData& inputData, std::string* outputData) { + const char* output = NULL; + unsigned outputSize = 0xFFFFFFFF; + + int result; + if (_step == 0) { + const char* actualMechanism; + result = sasl_client_start(_saslConnection, + getParameter(parameterMechanism).toString().c_str(), + NULL, + &output, + &outputSize, + &actualMechanism); + } + else { + result = sasl_client_step(_saslConnection, + inputData.rawData(), + static_cast<unsigned>(inputData.size()), + NULL, + &output, + &outputSize); + } + ++_step; + switch (result) { + case SASL_OK: + _done = true; + // Fall through + case SASL_CONTINUE: + *outputData = std::string(output, outputSize); + return Status::OK(); + case SASL_NOMECH: + return Status(ErrorCodes::BadValue, sasl_errdetail(_saslConnection)); + case SASL_BADAUTH: + return Status(ErrorCodes::AuthenticationFailed, sasl_errdetail(_saslConnection)); + default: + return Status(ErrorCodes::ProtocolError, sasl_errdetail(_saslConnection)); + } + } +} // namespace diff --git a/src/mongo/client/cyrus_sasl_client_session.h b/src/mongo/client/cyrus_sasl_client_session.h new file mode 100644 index 00000000000..2b6d04fe95f --- /dev/null +++ b/src/mongo/client/cyrus_sasl_client_session.h @@ -0,0 +1,85 @@ +/* Copyright 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/client/sasl_client_session.h" + +#include <sasl/sasl.h> + +namespace mongo { + + /** + * Implementation of the client side of a SASL authentication conversation. + * using the Cyrus SASL library. + */ + class MONGO_CLIENT_API CyrusSaslClientSession : public SaslClientSession { + MONGO_DISALLOW_COPYING(CyrusSaslClientSession); + public: + + CyrusSaslClientSession(); + ~CyrusSaslClientSession(); + + /** + * Overriding to store the password data in sasl_secret_t format + */ + virtual void setParameter(Parameter id, const StringData& value); + + /** + * Returns the value of the parameterPassword parameter in the form of a sasl_secret_t, used + * by the Cyrus SASL library's SASL_CB_PASS callback. The session object owns the storage + * referenced by the returned sasl_secret_t*, which will remain in scope according to the + * same rules as given for SaslClientSession::getParameter(). + */ + sasl_secret_t* getPasswordAsSecret(); + + virtual Status initialize(); + + virtual Status step(const StringData& inputData, std::string* outputData); + + virtual bool isDone() const { return _done; } + + private: + /// Maximum number of Cyrus SASL callbacks stored in _callbacks. + static const int maxCallbacks = 4; + + /// Underlying Cyrus SASL library connection object. + sasl_conn_t* _saslConnection; + + // Number of successfully completed conversation steps. + int _step; + + /// See isDone(). + bool _done; + + /// Stored of password in sasl_secret_t format + boost::scoped_array<char> _secret; + + /// Callbacks registered on _saslConnection for providing the Cyrus SASL library with + /// parameter values, etc. + sasl_callback_t _callbacks[maxCallbacks]; + }; + +} // namespace mongo diff --git a/src/mongo/client/native_sasl_client_session.cpp b/src/mongo/client/native_sasl_client_session.cpp new file mode 100644 index 00000000000..705626dc106 --- /dev/null +++ b/src/mongo/client/native_sasl_client_session.cpp @@ -0,0 +1,66 @@ +/* Copyright 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/client/native_sasl_client_session.h" + +#include "mongo/base/init.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace { + + SaslClientSession* createNativeSaslClientSession() { + return new NativeSaslClientSession(); + } + + MONGO_INITIALIZER(NativeSaslClientContext)(InitializerContext* context) { + SaslClientSession::create = createNativeSaslClientSession; + return Status::OK(); + } + +} // namespace + + NativeSaslClientSession::NativeSaslClientSession() : + SaslClientSession(), + _step(0), + _done(false) { + } + + NativeSaslClientSession::~NativeSaslClientSession() {} + + Status NativeSaslClientSession::initialize() { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "SASL authentication not supported in client"); + } + + Status NativeSaslClientSession::step(const StringData& inputData, std::string* outputData) { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "SASL authentication not supported in client"); + } +} // namespace diff --git a/src/mongo/client/native_sasl_client_session.h b/src/mongo/client/native_sasl_client_session.h new file mode 100644 index 00000000000..d80b9cc57ed --- /dev/null +++ b/src/mongo/client/native_sasl_client_session.h @@ -0,0 +1,57 @@ +/* Copyright 2014 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/client/sasl_client_session.h" + +namespace mongo { + + /** + * Implementation of the client side of a SASL authentication conversation using the + * native SASL implementation. + */ + class MONGO_CLIENT_API NativeSaslClientSession : public SaslClientSession { + MONGO_DISALLOW_COPYING(NativeSaslClientSession); + public: + + NativeSaslClientSession(); + ~NativeSaslClientSession(); + + virtual Status initialize(); + + virtual Status step(const StringData& inputData, std::string* outputData); + + virtual bool isDone() const { return _done; } + + private: + /// Number of successfully completed conversation steps. + int _step; + + /// See isDone(). + bool _done; + }; + +} // namespace mongo diff --git a/src/mongo/client/sasl_client_authenticate_impl.cpp b/src/mongo/client/sasl_client_authenticate_impl.cpp index 97d17467f4d..dd779a36884 100644 --- a/src/mongo/client/sasl_client_authenticate_impl.cpp +++ b/src/mongo/client/sasl_client_authenticate_impl.cpp @@ -196,21 +196,22 @@ namespace { return ex.toStatus(); } - SaslClientSession session; - Status status = configureSession(&session, client, targetDatabase, saslParameters); + boost::scoped_ptr<SaslClientSession> session(SaslClientSession::create()); + Status status = configureSession(session.get(), client, targetDatabase, saslParameters); + if (!status.isOK()) return status; BSONObj saslFirstCommandPrefix = BSON( saslStartCommandName << 1 << saslCommandMechanismFieldName << - session.getParameter(SaslClientSession::parameterMechanism)); + session->getParameter(SaslClientSession::parameterMechanism)); BSONObj saslFollowupCommandPrefix = BSON(saslContinueCommandName << 1); BSONObj saslCommandPrefix = saslFirstCommandPrefix; BSONObj inputObj = BSON(saslCommandPayloadFieldName << ""); bool isServerDone = false; - while (!session.isDone()) { + while (!session->isDone()) { std::string payload; BSONType type; @@ -221,7 +222,7 @@ namespace { LOG(saslLogLevel) << "sasl client input: " << base64::encode(payload) << endl; std::string responsePayload; - status = session.step(payload, &responsePayload); + status = session->step(payload, &responsePayload); if (!status.isOK()) return status; diff --git a/src/mongo/client/sasl_client_session.cpp b/src/mongo/client/sasl_client_session.cpp index 872c2043e18..06becc9b095 100644 --- a/src/mongo/client/sasl_client_session.cpp +++ b/src/mongo/client/sasl_client_session.cpp @@ -35,214 +35,22 @@ #include "mongo/util/signal_handlers_synchronous.h" namespace mongo { -namespace { + SaslClientSession::SaslClientSessionFactoryFn SaslClientSession::create = NULL; + + SaslClientSession::SaslClientSession() {} - /* - * Allocator functions to be used by the SASL library, if the client - * doesn't initialize the library for us. - */ - -// Version 2.1.26 is the first version to use size_t in the allocator signatures -#if (SASL_VERSION_FULL >= ((2 << 16) | (1 << 8) | 26)) - typedef size_t SaslAllocSize; -#else - typedef unsigned long SaslAllocSize; -#endif - - typedef int(*SaslCallbackFn)(); - - void* saslOurMalloc(SaslAllocSize sz) { - return mongoMalloc(sz); - } - - void* saslOurCalloc(SaslAllocSize count, SaslAllocSize size) { - void* ptr = calloc(count, size); - if (!ptr) { - reportOutOfMemoryErrorAndExit(); - } - return ptr; - } - - void* saslOurRealloc(void* ptr, SaslAllocSize sz) { - return mongoRealloc(ptr, sz); - } - - /* - * Mutex functions to be used by the SASL library, if the client doesn't initialize the library - * for us. - */ - - void* saslMutexAlloc(void) { - return new SimpleMutex("sasl"); - } - - int saslMutexLock(void* mutex) { - static_cast<SimpleMutex*>(mutex)->lock(); - return SASL_OK; - } - - int saslMutexUnlock(void* mutex) { - static_cast<SimpleMutex*>(mutex)->unlock(); - return SASL_OK; - } - - void saslMutexFree(void* mutex) { - delete static_cast<SimpleMutex*>(mutex); - } - - /** - * Configures the SASL library to use allocator and mutex functions we specify, - * unless the client application has previously initialized the SASL library. - */ - MONGO_INITIALIZER(CyrusSaslAllocatorsAndMutexes)(InitializerContext*) { - sasl_set_alloc(saslOurMalloc, - saslOurCalloc, - saslOurRealloc, - free); - - sasl_set_mutex(saslMutexAlloc, - saslMutexLock, - saslMutexUnlock, - saslMutexFree); - return Status::OK(); - } - - int saslClientLogSwallow(void *context, int priority, const char *message) { - return SASL_OK; // do nothing - } - - /** - * Initializes the client half of the SASL library, but is effectively a no-op if the client - * application has already done it. - * - * If a client wishes to override this initialization but keep the allocator and mutex - * initialization, it should implement a MONGO_INITIALIZER_GENERAL with - * CyrusSaslAllocatorsAndMutexes as a prerequisite and SaslClientContext as a dependent. If it - * wishes to override both, it should implement a MONGO_INITIALIZER_GENERAL with - * CyrusSaslAllocatorsAndMutexes and SaslClientContext as dependents, or initialize the library - * before calling mongo::runGlobalInitializersOrDie(). - */ - MONGO_INITIALIZER_WITH_PREREQUISITES(SaslClientContext, ("CyrusSaslAllocatorsAndMutexes"))( - InitializerContext* context) { - - static sasl_callback_t saslClientGlobalCallbacks[] = - { { SASL_CB_LOG, SaslCallbackFn(saslClientLogSwallow), NULL /* context */ }, - { SASL_CB_LIST_END } }; - - // If the client application has previously called sasl_client_init(), the callbacks passed - // in here are ignored. - // - // TODO: Call sasl_client_done() at shutdown when we have a story for orderly shutdown. - int result = sasl_client_init(saslClientGlobalCallbacks); - if (result != SASL_OK) { - return Status(ErrorCodes::UnknownError, - mongoutils::str::stream() << - "Could not initialize sasl client components (" << - sasl_errstring(result, NULL, NULL) << - ")"); - } - return Status::OK(); - } - - /** - * Callback registered on the sasl_conn_t underlying a SaslClientSession to allow the Cyrus SASL - * library to query for the authentication id and other simple string configuration parameters. - * - * Note that in Mongo, the authentication and authorization ids (authid and authzid) are always - * the same. These correspond to SASL_CB_AUTHNAME and SASL_CB_USER. - */ - int saslClientGetSimple(void* context, - int id, - const char** result, - unsigned* resultLen) throw () { - SaslClientSession* session = static_cast<SaslClientSession*>(context); - if (!session || !result) - return SASL_BADPARAM; - - SaslClientSession::Parameter requiredParameterId; - switch (id) { - case SASL_CB_AUTHNAME: - case SASL_CB_USER: - requiredParameterId = SaslClientSession::parameterUser; - break; - default: - return SASL_FAIL; - } - - if (!session->hasParameter(requiredParameterId)) - return SASL_FAIL; - StringData value = session->getParameter(requiredParameterId); - *result = value.rawData(); - if (resultLen) - *resultLen = static_cast<unsigned>(value.size()); - return SASL_OK; - } - - /** - * Callback registered on the sasl_conn_t underlying a SaslClientSession to allow the Cyrus SASL - * library to query for the password data. - */ - int saslClientGetPassword(sasl_conn_t* conn, - void* context, - int id, - sasl_secret_t** outSecret) throw () { - - SaslClientSession* session = static_cast<SaslClientSession*>(context); - if (!session || !outSecret) - return SASL_BADPARAM; - - sasl_secret_t* secret = session->getPasswordAsSecret(); - if (secret == NULL) { - sasl_seterror(conn, 0, "No password data provided"); - return SASL_FAIL; - } - - *outSecret = secret; - return SASL_OK; - } - -} // namespace - - SaslClientSession::SaslClientSession() : - _saslConnection(NULL), - _step(0), - _done(false) { - - const sasl_callback_t callbackTemplate[maxCallbacks] = { - { SASL_CB_AUTHNAME, SaslCallbackFn(saslClientGetSimple), this }, - { SASL_CB_USER, SaslCallbackFn(saslClientGetSimple), this }, - { SASL_CB_PASS, SaslCallbackFn(saslClientGetPassword), this }, - { SASL_CB_LIST_END } - }; - std::copy(callbackTemplate, callbackTemplate + maxCallbacks, _callbacks); - } - - SaslClientSession::~SaslClientSession() { - sasl_dispose(&_saslConnection); - } + SaslClientSession::~SaslClientSession() {} void SaslClientSession::setParameter(Parameter id, const StringData& value) { fassert(16807, id >= 0 && id < numParameters); DataBuffer& buffer = _parameters[id]; - if (id == parameterPassword) { - // The parameterPassword is stored as a sasl_secret_t inside its DataBuffer, while other - // parameters are stored directly. This facilitates memory ownership management for - // getPasswordAsSecret(). - buffer.size = sizeof(sasl_secret_t) + value.size(); - buffer.data.reset(new char[buffer.size + 1]); - sasl_secret_t* secret = - static_cast<sasl_secret_t*>(static_cast<void*>(buffer.data.get())); - secret->len = value.size(); - value.copyTo(static_cast<char*>(static_cast<void*>(&secret->data[0])), false); - } - else { - buffer.size = value.size(); - buffer.data.reset(new char[buffer.size + 1]); - // Note that we append a terminal NUL to buffer.data, so it may be treated as a C-style - // string. This is required for parameterServiceName, parameterServiceHostname, - // parameterMechanism and parameterUser. - value.copyTo(buffer.data.get(), true); - } + buffer.size = value.size(); + buffer.data.reset(new char[buffer.size + 1]); + + // Note that we append a terminal NUL to buffer.data, so it may be treated as a C-style + // string. This is required for parameterServiceName, parameterServiceHostname, + // parameterMechanism and parameterUser. + value.copyTo(buffer.data.get(), true); } bool SaslClientSession::hasParameter(Parameter id) { @@ -255,80 +63,8 @@ namespace { if (!hasParameter(id)) return StringData(); - if (id == parameterPassword) { - // See comment in setParameter() about the special storage of parameterPassword. - sasl_secret_t* secret = getPasswordAsSecret(); - return StringData(static_cast<char*>(static_cast<void*>(secret->data)), secret->len); - } - else { - DataBuffer& buffer = _parameters[id]; - return StringData(buffer.data.get(), buffer.size); - } - } - - sasl_secret_t* SaslClientSession::getPasswordAsSecret() { - // See comment in setParameter() about the special storage of parameterPassword. - return static_cast<sasl_secret_t*>( - static_cast<void*>(_parameters[parameterPassword].data.get())); - } - - Status SaslClientSession::initialize() { - if (_saslConnection != NULL) - return Status(ErrorCodes::AlreadyInitialized, "Cannot reinitialize SaslClientSession."); - - int result = sasl_client_new(_parameters[parameterServiceName].data.get(), - _parameters[parameterServiceHostname].data.get(), - NULL, - NULL, - _callbacks, - 0, - &_saslConnection); - - if (SASL_OK != result) { - return Status(ErrorCodes::UnknownError, - mongoutils::str::stream() << sasl_errstring(result, NULL, NULL)); - } - - return Status::OK(); - } - - Status SaslClientSession::step(const StringData& inputData, std::string* outputData) { - const char* output = NULL; - unsigned outputSize = 0xFFFFFFFF; - - int result; - if (_step == 0) { - const char* actualMechanism; - result = sasl_client_start(_saslConnection, - getParameter(parameterMechanism).toString().c_str(), - NULL, - &output, - &outputSize, - &actualMechanism); - } - else { - result = sasl_client_step(_saslConnection, - inputData.rawData(), - static_cast<unsigned>(inputData.size()), - NULL, - &output, - &outputSize); - } - ++_step; - switch (result) { - case SASL_OK: - _done = true; - // Fall through - case SASL_CONTINUE: - *outputData = std::string(output, outputSize); - return Status::OK(); - case SASL_NOMECH: - return Status(ErrorCodes::BadValue, sasl_errdetail(_saslConnection)); - case SASL_BADAUTH: - return Status(ErrorCodes::AuthenticationFailed, sasl_errdetail(_saslConnection)); - default: - return Status(ErrorCodes::ProtocolError, sasl_errdetail(_saslConnection)); - } + DataBuffer& buffer = _parameters[id]; + return StringData(buffer.data.get(), buffer.size); } } // namespace mongo diff --git a/src/mongo/client/sasl_client_session.h b/src/mongo/client/sasl_client_session.h index 552c0653fef..8f06bcbe6ab 100644 --- a/src/mongo/client/sasl_client_session.h +++ b/src/mongo/client/sasl_client_session.h @@ -26,19 +26,18 @@ */ #include <boost/scoped_array.hpp> -#include <sasl/sasl.h> #include <string> -#include <vector> #include "mongo/base/disallow_copying.h" #include "mongo/base/status.h" #include "mongo/base/string_data.h" #include "mongo/client/export_macros.h" +#include "mongo/stdx/functional.h" namespace mongo { /** - * Implementation of the client side of a SASL authentication conversation. + * Base class for the client side of a SASL authentication conversation. * * To use, create an instance, then use setParameter() to configure the authentication * parameters. Once all parameters are set, call initialize() to initialize the client state @@ -53,6 +52,9 @@ namespace mongo { class MONGO_CLIENT_API SaslClientSession { MONGO_DISALLOW_COPYING(SaslClientSession); public: + typedef stdx::function<SaslClientSession* ()> SaslClientSessionFactoryFn; + static SaslClientSessionFactoryFn create; + /** * Identifiers of parameters used to configure a SaslClientSession. */ @@ -66,7 +68,7 @@ namespace mongo { }; SaslClientSession(); - ~SaslClientSession(); + virtual ~SaslClientSession(); /** * Sets the parameter identified by "id" to "value". @@ -77,12 +79,12 @@ namespace mongo { * * The session object makes and owns a copy of the data in "value". */ - void setParameter(Parameter id, const StringData& value); + virtual void setParameter(Parameter id, const StringData& value); /** * Returns true if "id" identifies a parameter previously set by a call to setParameter(). */ - bool hasParameter(Parameter id); + virtual bool hasParameter(Parameter id); /** * Returns the value of a previously set parameter. @@ -94,22 +96,14 @@ namespace mongo { * valid until setParameter() is called with the same value of "id", or the session object * goes out of scope. */ - StringData getParameter(Parameter id); - - /** - * Returns the value of the parameterPassword parameter in the form of a sasl_secret_t, used - * by the Cyrus SASL library's SASL_CB_PASS callback. The session object owns the storage - * referenced by the returned sasl_secret_t*, which will remain in scope according to the - * same rules as given for getParameter(), above. - */ - sasl_secret_t* getPasswordAsSecret(); + virtual StringData getParameter(Parameter id); /** * Initializes a session for use. * * Call exactly once, after setting any parameters you intend to set via setParameter(). */ - Status initialize(); + virtual Status initialize() = 0; /** * Takes one step of the SASL protocol on behalf of the client. @@ -126,12 +120,12 @@ namespace mongo { * determine if the conversation has completed. When step() returns Status::OK() and * isDone() returns true, authentication has completed successfully. */ - Status step(const StringData& inputData, std::string* outputData); + virtual Status step(const StringData& inputData, std::string* outputData) = 0; /** * Returns true if the authentication completed successfully. */ - bool isDone() const { return _done; } + virtual bool isDone() const = 0; private: /** @@ -142,24 +136,8 @@ namespace mongo { size_t size; }; - /// Maximum number of Cyrus SASL callbacks stored in _callbacks. - static const int maxCallbacks = 4; - - /// Underlying Cyrus SASL library connection object. - sasl_conn_t* _saslConnection; - - /// Callbacks registered on _saslConnection for providing the Cyrus SASL library with - /// parameter values, etc. - sasl_callback_t _callbacks[maxCallbacks]; - /// Buffers for each of the settable parameters. DataBuffer _parameters[numParameters]; - - /// Number of successfully completed conversation steps. - int _step; - - /// See isDone(). - bool _done; }; } // namespace mongo diff --git a/src/mongo/client/sasl_sspi.cpp b/src/mongo/client/sasl_sspi.cpp index 1b08624bcb6..b6d72e01f46 100644 --- a/src/mongo/client/sasl_sspi.cpp +++ b/src/mongo/client/sasl_sspi.cpp @@ -492,7 +492,7 @@ namespace { */ MONGO_INITIALIZER_WITH_PREREQUISITES(SaslSspiClientPlugin, ("CyrusSaslAllocatorsAndMutexes", - "SaslClientContext")) + "CyrusSaslClientContext")) (InitializerContext*) { int ret = sasl_client_add_plugin(sspiPluginName, @@ -507,7 +507,7 @@ namespace { } MONGO_INITIALIZER_WITH_PREREQUISITES(SaslCramClientPlugin, ("CyrusSaslAllocatorsAndMutexes", - "SaslClientContext")) + "CyrusSaslClientContext")) (InitializerContext*) { int ret = sasl_client_add_plugin("CRAMMD5", crammd5_client_plug_init); @@ -522,7 +522,7 @@ namespace { MONGO_INITIALIZER_WITH_PREREQUISITES(SaslPlainClientPlugin, ("CyrusSaslAllocatorsAndMutexes", - "SaslClientContext")) + "CyrusSaslClientContext")) (InitializerContext*) { int ret = sasl_client_add_plugin("PLAIN", plain_client_plug_init); diff --git a/src/mongo/db/auth/sasl_authentication_session.cpp b/src/mongo/db/auth/sasl_authentication_session.cpp index 57d842999ec..4fba45521fb 100644 --- a/src/mongo/db/auth/sasl_authentication_session.cpp +++ b/src/mongo/db/auth/sasl_authentication_session.cpp @@ -47,7 +47,8 @@ #include "mongo/util/mongoutils/str.h" namespace mongo { - SaslAuthenticationSession::SaslSessionFactoryFn SaslAuthenticationSession::create = NULL; + SaslAuthenticationSession::SaslAuthenticationSessionFactoryFn + SaslAuthenticationSession::create = NULL; // Mechanism name constants. const char SaslAuthenticationSession::mechanismCRAMMD5[] = "CRAM-MD5"; diff --git a/src/mongo/db/auth/sasl_authentication_session.h b/src/mongo/db/auth/sasl_authentication_session.h index e4b8ac42655..05c8aa8afab 100644 --- a/src/mongo/db/auth/sasl_authentication_session.h +++ b/src/mongo/db/auth/sasl_authentication_session.h @@ -51,8 +51,8 @@ namespace mongo { MONGO_DISALLOW_COPYING(SaslAuthenticationSession); public: typedef stdx::function<SaslAuthenticationSession* (AuthorizationSession*)> - SaslSessionFactoryFn; - static SaslSessionFactoryFn create; + SaslAuthenticationSessionFactoryFn; + static SaslAuthenticationSessionFactoryFn create; // Mechanism name constants. static const char mechanismCRAMMD5[]; |