summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Schwerin <schwerin@10gen.com>2013-03-28 15:22:26 -0400
committerAndy Schwerin <schwerin@10gen.com>2013-04-22 13:02:47 -0400
commit2d8e9831979076e5f6a47895aedad80c8da10d4c (patch)
treeab35ee725cb594b5685a544429a48bc9bfd034cc
parent506e7c0adffc8708fb15bab929fa83d016e96eec (diff)
downloadmongo-2d8e9831979076e5f6a47895aedad80c8da10d4c.tar.gz
SERVER-8813 Switch C++ client and shell code for SASL authentication to use the Cyrus SASL library.
Replaces gsasl implementation in clients.
-rw-r--r--SConstruct2
-rwxr-xr-xdistsrc/client/SConstruct2
-rw-r--r--src/SConscript.client2
-rw-r--r--src/mongo/SConscript4
-rw-r--r--src/mongo/client/dbclient.cpp2
-rw-r--r--src/mongo/client/sasl_client_authenticate.cpp3
-rw-r--r--src/mongo/client/sasl_client_authenticate.h14
-rw-r--r--src/mongo/client/sasl_client_authenticate_impl.cpp280
-rw-r--r--src/mongo/client/sasl_client_session.cpp213
-rw-r--r--src/mongo/client/sasl_client_session.h152
-rw-r--r--src/mongo/util/gsasl_session.cpp101
-rw-r--r--src/mongo/util/gsasl_session.h147
12 files changed, 571 insertions, 351 deletions
diff --git a/SConstruct b/SConstruct
index 50df9281479..3f2634d120b 100644
--- a/SConstruct
+++ b/SConstruct
@@ -1132,7 +1132,7 @@ def doConfigure(myenv):
conf.env['MONGO_BUILD_SASL_CLIENT'] = bool(has_option("use-sasl-client"))
if conf.env['MONGO_BUILD_SASL_CLIENT'] and not conf.CheckLibWithHeader(
- "gsasl", "gsasl.h", "C", "gsasl_check_version(GSASL_VERSION);", autoadd=False):
+ "sasl2", "sasl/sasl.h", "C", "sasl_version_info(0, 0, 0, 0, 0, 0);", autoadd=False):
Exit(1)
diff --git a/distsrc/client/SConstruct b/distsrc/client/SConstruct
index 0bf69a59e79..e9fac9305aa 100755
--- a/distsrc/client/SConstruct
+++ b/distsrc/client/SConstruct
@@ -75,7 +75,7 @@ for lib in boostLibs:
Exit(1)
env['MONGO_BUILD_SASL_CLIENT'] = conf.CheckLibWithHeader(
- "gsasl", "gsasl.h", "C", "gsasl_check_version(GSASL_VERSION);", autoadd=False)
+ "sasl2", "sasl/sasl.h", "C", "sasl_version_info(0, 0, 0, 0, 0, 0);", autoadd=False)
conf.Finish()
diff --git a/src/SConscript.client b/src/SConscript.client
index b88a54f40e5..0959a12c597 100644
--- a/src/SConscript.client
+++ b/src/SConscript.client
@@ -87,7 +87,7 @@ clientSourceBasic = [
]
clientSourceSasl = ['mongo/client/sasl_client_authenticate_impl.cpp',
- 'mongo/util/gsasl_session.cpp']
+ 'mongo/client/sasl_client_session.cpp']
clientSourceAll = clientSourceBasic + clientSourceSasl
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 0f2a25f2f85..e3aa52cf450 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -167,8 +167,8 @@ commonSysLibdeps = []
if env['MONGO_BUILD_SASL_CLIENT']:
commonFiles.extend(['client/sasl_client_authenticate_impl.cpp',
- 'util/gsasl_session.cpp'])
- commonSysLibdeps.append('gsasl')
+ 'client/sasl_client_session.cpp'])
+ commonSysLibdeps.append('sasl2')
# handle processinfo*
processInfoFiles = [ "util/processinfo.cpp" ]
diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp
index 03f1924b90d..1b6fba68d99 100644
--- a/src/mongo/client/dbclient.cpp
+++ b/src/mongo/client/dbclient.cpp
@@ -568,7 +568,7 @@ namespace mongo {
_authMongoCR(userSource, user, password, errmsg, digestPassword));
}
else if (saslClientAuthenticate != NULL) {
- uassertStatusOK(saslClientAuthenticate(this, params, NULL));
+ uassertStatusOK(saslClientAuthenticate(this, params));
}
else {
uasserted(ErrorCodes::BadValue,
diff --git a/src/mongo/client/sasl_client_authenticate.cpp b/src/mongo/client/sasl_client_authenticate.cpp
index b97e97583a4..24cd5000fd7 100644
--- a/src/mongo/client/sasl_client_authenticate.cpp
+++ b/src/mongo/client/sasl_client_authenticate.cpp
@@ -27,8 +27,7 @@ namespace mongo {
using namespace mongoutils;
Status (*saslClientAuthenticate)(DBClientWithCommands* client,
- const BSONObj& saslParameters,
- void* sessionHook) = NULL;
+ const BSONObj& saslParameters) = NULL;
const char* const saslStartCommandName = "saslStart";
const char* const saslContinueCommandName = "saslContinue";
diff --git a/src/mongo/client/sasl_client_authenticate.h b/src/mongo/client/sasl_client_authenticate.h
index f6d550abe87..c95cdc42660 100644
--- a/src/mongo/client/sasl_client_authenticate.h
+++ b/src/mongo/client/sasl_client_authenticate.h
@@ -34,34 +34,28 @@ namespace mongo {
*
* The "saslParameters" BSONObj should be initialized with zero or more of the
* fields below. Which fields are required depends on the mechanism. Consult the
- * libgsasl documentation.
+ * relevant IETF standards.
*
* "mechanism": The string name of the sasl mechanism to use. Mandatory.
* "autoAuthorize": Truthy values tell the server to automatically acquire privileges on
* all resources after successful authentication, which is the default. Falsey values
* instruct the server to await separate privilege-acquisition commands.
- * "user": The string name of the principal to authenticate, GSASL_AUTHID.
+ * "user": The string name of the principal to authenticate.
* "userSource": The database target of the auth command, which identifies the location
* of the credential information for the principal. May be "$external" if credential
* information is stored outside of the mongo cluster.
- * "pwd": The password data, GSASL_PASSWORD.
+ * "pwd": The password.
* "serviceName": The GSSAPI service name to use. Defaults to "mongodb".
* "serviceHostname": The GSSAPI hostname to use. Defaults to the name of the remote host.
*
* Other fields in saslParameters are silently ignored.
*
- * "sessionHook" is a pointer to optional data, which may be used by the gsasl_callback
- * previously set on "gsasl". The session hook is set on an underlying Gsasl_session using
- * gsasl_session_hook_set, and may be accessed by callbacks using gsasl_session_hook_get.
- * See the gsasl documentation.
- *
* Returns an OK status on success, and ErrorCodes::AuthenticationFailed if authentication is
* rejected. Other failures, all of which are tantamount to authentication failure, may also be
* returned.
*/
extern Status (*saslClientAuthenticate)(DBClientWithCommands* client,
- const BSONObj& saslParameters,
- void* sessionHook);
+ const BSONObj& saslParameters);
/**
* Extracts the payload field from "cmdObj", and store it into "*payload".
diff --git a/src/mongo/client/sasl_client_authenticate_impl.cpp b/src/mongo/client/sasl_client_authenticate_impl.cpp
index b7aaa218d10..f33fd9bdfa4 100644
--- a/src/mongo/client/sasl_client_authenticate_impl.cpp
+++ b/src/mongo/client/sasl_client_authenticate_impl.cpp
@@ -13,22 +13,31 @@
* limitations under the License.
*/
+/**
+ * This module implements the client side of SASL authentication in MongoDB, in terms of the Cyrus
+ * SASL library. See <sasl/sasl.h> and http://cyrusimap.web.cmu.edu/ for relevant documentation.
+ *
+ * The primary entry point at runtime is saslClientAuthenticateImpl().
+ */
+
+#include <boost/scoped_ptr.hpp>
#include <string>
+#include <sasl/sasl.h>
#include "mongo/base/init.h"
#include "mongo/base/status.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/util/bson_extract.h"
#include "mongo/client/sasl_client_authenticate.h"
+#include "mongo/client/sasl_client_session.h"
#include "mongo/platform/cstdint.h"
+#include "mongo/util/allocator.h"
#include "mongo/util/base64.h"
-#include "mongo/util/gsasl_session.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
+#include "mongo/util/concurrency/mutex.h"
#include "mongo/util/net/hostandport.h"
-#include <gsasl.h> // Must be included after "mongo/platform/cstdint.h" because of SERVER-8086.
-
namespace mongo {
namespace {
@@ -37,134 +46,235 @@ namespace {
const char* const saslClientLogFieldName = "clientLogLevel";
- Gsasl* _gsaslLibraryContext = NULL;
+ /*
+ * Allocator functions to be used by the SASL library, if the client
+ * doesn't initialize the library for us.
+ */
- MONGO_INITIALIZER(SaslClientContext)(InitializerContext* context) {
- fassert(16710, _gsaslLibraryContext == NULL);
+ void* saslOurMalloc(unsigned long sz) {
+ return ourmalloc(sz);
+ }
- if (!gsasl_check_version(GSASL_VERSION))
- return Status(ErrorCodes::UnknownError, "Incompatible gsasl library.");
+ void* saslOurCalloc(unsigned long count, unsigned long size) {
+ void* ptr = calloc(count, size);
+ if (!ptr) printStackAndExit(0);
+ return ptr;
+ }
+
+ void* saslOurRealloc(void* ptr, unsigned long sz) {
+ return ourrealloc(ptr, sz);
+ }
+
+ /*
+ * Mutex functions to be used by the SASL library, if the client doesn't initialize the library
+ * for us.
+ */
- int rc = gsasl_init(&_gsaslLibraryContext);
- if (GSASL_OK != rc)
- return Status(ErrorCodes::UnknownError, gsasl_strerror(rc));
+ 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();
}
/**
- * Configure "*session" as a client gsasl session for authenticating on the connection
- * "*client", with the given "saslParameters". "gsasl" and "sessionHook" are passed through
- * to GsaslSession::initializeClientSession, where they are documented.
+ * 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().
*/
- Status configureSession(Gsasl* gsasl,
- DBClientWithCommands* client,
- const BSONObj& saslParameters,
- void* sessionHook,
- GsaslSession* session) {
+ MONGO_INITIALIZER_WITH_PREREQUISITES(SaslClientContext, ("CyrusSaslAllocatorsAndMutexes"))(
+ InitializerContext* context) {
+
+ static sasl_callback_t saslClientGlobalCallbacks[] = { { 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();
+ }
+
+ int getSaslClientLogLevel(const BSONObj& saslParameters) {
+ int saslLogLevel = defaultSaslClientLogLevel;
+ BSONElement saslLogElement = saslParameters[saslClientLogFieldName];
+ if (saslLogElement.trueValue())
+ saslLogLevel = 1;
+ if (saslLogElement.isNumber())
+ saslLogLevel = saslLogElement.numberInt();
+ return saslLogLevel;
+ }
+
+ /**
+ * Gets the password data from "saslParameters" and stores it to "outPassword".
+ *
+ * If "saslParameters" indicates that the password needs to be "digested" via
+ * DBClientWithCommands::createPasswordDigest(), this method takes care of that.
+ * On success, the value of "*outPassword" is always the correct value to set
+ * as the password on the SaslClientSession.
+ *
+ * Returns Status::OK() on success, and ErrorCodes::NoSuchKey if the password data is not
+ * present in "saslParameters". Other ErrorCodes returned indicate other errors.
+ */
+ Status extractPassword(DBClientWithCommands* client,
+ const BSONObj& saslParameters,
+ std::string* outPassword) {
- std::string mechanism;
+ std::string rawPassword;
Status status = bsonExtractStringField(saslParameters,
- saslCommandMechanismFieldName,
- &mechanism);
+ saslCommandPasswordFieldName,
+ &rawPassword);
+ if (!status.isOK())
+ return status;
+
+ bool digest;
+ status = bsonExtractBooleanFieldWithDefault(saslParameters,
+ saslCommandDigestPasswordFieldName,
+ true,
+ &digest);
if (!status.isOK())
return status;
- status = session->initializeClientSession(gsasl, mechanism, sessionHook);
+ if (digest) {
+ std::string user;
+ status = bsonExtractStringField(saslParameters,
+ saslCommandPrincipalFieldName,
+ &user);
+ if (!status.isOK())
+ return status;
+
+ *outPassword = client->createPasswordDigest(user, rawPassword);
+ }
+ else {
+ *outPassword = rawPassword;
+ }
+ return Status::OK();
+ }
+
+ /**
+ * Configures "session" to perform the client side of a SASL conversation over connection
+ * "client".
+ *
+ * "saslParameters" is a BSON document providing the necessary configuration information.
+ *
+ * Returns Status::OK() on success.
+ */
+ Status configureSession(SaslClientSession* session,
+ DBClientWithCommands* client,
+ const BSONObj& saslParameters) {
+
+ std::string value;
+ Status status = bsonExtractStringField(saslParameters,
+ saslCommandMechanismFieldName,
+ &value);
if (!status.isOK())
return status;
+ session->setParameter(SaslClientSession::parameterMechanism, value);
- std::string service;
status = bsonExtractStringFieldWithDefault(saslParameters,
saslCommandServiceNameFieldName,
saslDefaultServiceName,
- &service);
+ &value);
if (!status.isOK())
return status;
- session->setProperty(GSASL_SERVICE, service);
+ session->setParameter(SaslClientSession::parameterServiceName, value);
- std::string hostname;
status = bsonExtractStringFieldWithDefault(saslParameters,
saslCommandServiceHostnameFieldName,
HostAndPort(client->getServerAddress()).host(),
- &hostname);
+ &value);
if (!status.isOK())
return status;
- session->setProperty(GSASL_HOSTNAME, hostname);
-
- BSONElement principalElement = saslParameters[saslCommandPrincipalFieldName];
- if (principalElement.type() == String) {
- session->setProperty(GSASL_AUTHID, principalElement.str());
- }
- else if (!principalElement.eoo()) {
- return Status(ErrorCodes::TypeMismatch,
- str::stream() << "Expected string for " << principalElement);
- }
+ session->setParameter(SaslClientSession::parameterServiceHostname, value);
- BSONElement passwordElement = saslParameters[saslCommandPasswordFieldName];
- if (passwordElement.type() == String) {
- bool digest;
- status = bsonExtractBooleanFieldWithDefault(saslParameters,
- saslCommandDigestPasswordFieldName,
- true,
- &digest);
- if (!status.isOK())
- return status;
+ status = bsonExtractStringField(saslParameters,
+ saslCommandPrincipalFieldName,
+ &value);
+ if (!status.isOK())
+ return status;
+ session->setParameter(SaslClientSession::parameterUser, value);
- std::string passwordHash;
- if (digest) {
- passwordHash = client->createPasswordDigest(principalElement.str(),
- passwordElement.str());
- }
- else {
- passwordHash = passwordElement.str();
- }
- session->setProperty(GSASL_PASSWORD, passwordHash);
+ status = extractPassword(client, saslParameters, &value);
+ if (status.isOK()) {
+ session->setParameter(SaslClientSession::parameterPassword, value);
}
- else if (!passwordElement.eoo()) {
- return Status(ErrorCodes::TypeMismatch,
- str::stream() << "Expected string for " << passwordElement);
+ else if (status != ErrorCodes::NoSuchKey) {
+ return status;
}
- return Status::OK();
- }
-
- int getSaslClientLogLevel(const BSONObj& saslParameters) {
- int saslLogLevel = defaultSaslClientLogLevel;
- BSONElement saslLogElement = saslParameters[saslClientLogFieldName];
- if (saslLogElement.trueValue())
- saslLogLevel = 1;
- if (saslLogElement.isNumber())
- saslLogLevel = saslLogElement.numberInt();
- return saslLogLevel;
+ return session->initialize();
}
- Status saslClientAuthenticateImpl(DBClientWithCommands* client,
- const BSONObj& saslParameters,
- void* sessionHook) {
-
- GsaslSession session;
+ /**
+ * Driver for the client side of a sasl authentication session, conducted synchronously over
+ * "client".
+ */
+ Status saslClientAuthenticateImpl(DBClientWithCommands* client, const BSONObj& saslParameters) {
int saslLogLevel = getSaslClientLogLevel(saslParameters);
- Status status = configureSession(_gsaslLibraryContext,
- client,
- saslParameters,
- sessionHook,
- &session);
+ SaslClientSession session;
+ Status status = configureSession(&session, client, saslParameters);
if (!status.isOK())
return status;
std::string targetDatabase;
- status = bsonExtractStringFieldWithDefault(saslParameters,
- saslCommandPrincipalSourceFieldName,
- saslDefaultDBName,
- &targetDatabase);
+ try {
+ status = bsonExtractStringFieldWithDefault(saslParameters,
+ saslCommandPrincipalSourceFieldName,
+ saslDefaultDBName,
+ &targetDatabase);
+ } catch (const DBException& ex) {
+ return ex.toStatus();
+ }
if (!status.isOK())
return status;
BSONObj saslFirstCommandPrefix = BSON(
saslStartCommandName << 1 <<
- saslCommandMechanismFieldName << session.getMechanism());
+ saslCommandMechanismFieldName <<
+ session.getParameter(SaslClientSession::parameterMechanism));
BSONObj saslFollowupCommandPrefix = BSON(saslContinueCommandName << 1);
BSONObj saslCommandPrefix = saslFirstCommandPrefix;
diff --git a/src/mongo/client/sasl_client_session.cpp b/src/mongo/client/sasl_client_session.cpp
new file mode 100644
index 00000000000..a0b36420384
--- /dev/null
+++ b/src/mongo/client/sasl_client_session.cpp
@@ -0,0 +1,213 @@
+/* Copyright 2012 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mongo/client/sasl_client_session.h"
+
+#include "mongo/util/assert_util.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace {
+
+ /**
+ * 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) {
+
+ typedef int(*SaslCallbackFn)();
+
+ 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);
+ }
+
+ void SaslClientSession::setParameter(Parameter id, const StringData& value) {
+ fassert(16805, 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);
+ }
+ }
+
+ bool SaslClientSession::hasParameter(Parameter id) {
+ if (id < 0 || id >= numParameters)
+ return false;
+ return _parameters[id].data;
+ }
+
+ StringData SaslClientSession::getParameter(Parameter id) {
+ 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));
+ }
+ }
+
+} // namespace mongo
diff --git a/src/mongo/client/sasl_client_session.h b/src/mongo/client/sasl_client_session.h
new file mode 100644
index 00000000000..9316b7c84a1
--- /dev/null
+++ b/src/mongo/client/sasl_client_session.h
@@ -0,0 +1,152 @@
+/* Copyright 2012 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#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"
+
+namespace mongo {
+
+ /**
+ * Implementation of 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
+ * machine. Finally, use repeated calls to step() to generate messages to send to the server
+ * and process server responses.
+ *
+ * The required parameters vary by mechanism, but all mechanisms require parameterServiceName,
+ * parameterServiceHostname, parameterMechanism and parameterUser. All of the required
+ * parameters must be UTF-8 encoded strings with no embedded NUL characters. The
+ * parameterPassword parameter is not constrained.
+ */
+ class SaslClientSession {
+ MONGO_DISALLOW_COPYING(SaslClientSession);
+ public:
+ /**
+ * Identifiers of parameters used to configure a SaslClientSession.
+ */
+ enum Parameter {
+ parameterServiceName = 0,
+ parameterServiceHostname,
+ parameterMechanism,
+ parameterUser,
+ parameterPassword,
+ numParameters // Must be last
+ };
+
+ SaslClientSession();
+ ~SaslClientSession();
+
+ /**
+ * Sets the parameter identified by "id" to "value".
+ *
+ * The value of "id" must be one of the legal values of Parameter less than numParameters.
+ * May be called repeatedly for the same value of "id", with the last "value" replacing
+ * previous values.
+ *
+ * The session object makes and owns a copy of the data in "value".
+ */
+ 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);
+
+ /**
+ * Returns the value of a previously set parameter.
+ *
+ * If parameter "id" was never set, returns an empty StringData. Note that a parameter may
+ * be explicitly set to StringData(), so use hasParameter() to distinguish those cases.
+ *
+ * The session object owns the storage behind the returned StringData, which will remain
+ * 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();
+
+ /**
+ * Initializes a session for use.
+ *
+ * Call exactly once, after setting any parameters you intend to set via setParameter().
+ */
+ Status initialize();
+
+ /**
+ * Takes one step of the SASL protocol on behalf of the client.
+ *
+ * Caller should provide data from the server side of the conversation in "inputData", or an
+ * empty StringData() if none is available. If the client should make a response to the
+ * server, stores the response into "*outputData".
+ *
+ * Returns Status::OK() on success. Any other return value indicates a failed
+ * authentication, though the specific return value may provide insight into the cause of
+ * the failure (e.g., ProtocolError, AuthenticationFailed).
+ *
+ * In the event that this method returns Status::OK(), consult the value of isDone() to
+ * 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);
+
+ /**
+ * Returns true if the authentication completed successfully.
+ */
+ bool isDone() const { return _done; }
+
+ private:
+ /**
+ * Buffer object that owns data for a single parameter.
+ */
+ struct DataBuffer {
+ boost::scoped_array<char> data;
+ 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/util/gsasl_session.cpp b/src/mongo/util/gsasl_session.cpp
deleted file mode 100644
index de8be43ffb7..00000000000
--- a/src/mongo/util/gsasl_session.cpp
+++ /dev/null
@@ -1,101 +0,0 @@
-/* Copyright 2012 10gen Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "mongo/util/gsasl_session.h"
-
-#include <cstdlib>
-
-#include "mongo/util/assert_util.h"
-
-namespace mongo {
-
- GsaslSession::GsaslSession() : _gsaslSession(NULL), _done(false) {}
-
- GsaslSession::~GsaslSession() {
- if (_gsaslSession)
- gsasl_finish(_gsaslSession);
- }
-
- std::string GsaslSession::getMechanism() const {
- return gsasl_mechanism_name(_gsaslSession);
- }
-
- void GsaslSession::setProperty(Gsasl_property property, const StringData& value) {
- gsasl_property_set_raw(_gsaslSession, property, value.rawData(), value.size());
- }
-
- const std::string GsaslSession::getProperty(Gsasl_property property) const {
- const char* prop = gsasl_property_fast(_gsaslSession, property);
- if (prop == NULL) {
- return "";
- }
- return prop;
- }
-
- Status GsaslSession::initializeClientSession(Gsasl* gsasl,
- const StringData& mechanism,
- void* sessionHook) {
- return _initializeSession(&gsasl_client_start, gsasl, mechanism, sessionHook);
- }
-
- Status GsaslSession::initializeServerSession(Gsasl* gsasl,
- const StringData& mechanism,
- void* sessionHook) {
- return _initializeSession(&gsasl_server_start, gsasl, mechanism, sessionHook);
- }
-
- Status GsaslSession::_initializeSession(
- GsaslSessionStartFn sessionStartFn,
- Gsasl* gsasl, const StringData& mechanism, void* sessionHook) {
-
- if (_done || _gsaslSession)
- return Status(ErrorCodes::CannotReuseObject, "Cannot reuse GsaslSession.");
-
- int rc = sessionStartFn(gsasl, mechanism.toString().c_str(), &_gsaslSession);
- switch (rc) {
- case GSASL_OK:
- gsasl_session_hook_set(_gsaslSession, sessionHook);
- return Status::OK();
- case GSASL_UNKNOWN_MECHANISM:
- return Status(ErrorCodes::BadValue, gsasl_strerror(rc));
- default:
- return Status(ErrorCodes::ProtocolError, gsasl_strerror(rc));
- }
- }
-
- Status GsaslSession::step(const StringData& inputData, std::string* outputData) {
- char* output;
- size_t outputSize;
- int rc = gsasl_step(_gsaslSession,
- inputData.rawData(), inputData.size(),
- &output, &outputSize);
-
- if (GSASL_OK == rc)
- _done = true;
-
- switch (rc) {
- case GSASL_OK:
- case GSASL_NEEDS_MORE:
- *outputData = std::string(output, output + outputSize);
- free(output);
- return Status::OK();
- case GSASL_AUTHENTICATION_ERROR:
- return Status(ErrorCodes::AuthenticationFailed, gsasl_strerror(rc));
- default:
- return Status(ErrorCodes::ProtocolError, gsasl_strerror(rc));
- }
- }
-
-} // namespace mongo
diff --git a/src/mongo/util/gsasl_session.h b/src/mongo/util/gsasl_session.h
deleted file mode 100644
index cdaa9b58a5a..00000000000
--- a/src/mongo/util/gsasl_session.h
+++ /dev/null
@@ -1,147 +0,0 @@
-/* Copyright 2012 10gen Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <string>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/base/status.h"
-#include "mongo/base/string_data.h"
-#include "mongo/platform/cstdint.h" // Must be included before <gsasl.h> because of SERVER-8086
-
-#include <gsasl.h> // Must be included after "mongo/platform/cstdint.h" because of SERVER-8086.
-
-namespace mongo {
-
- /**
- * C++ wrapper around Gsasl_session.
- */
- class GsaslSession {
- MONGO_DISALLOW_COPYING(GsaslSession);
- public:
- GsaslSession();
- ~GsaslSession();
-
- /**
- * Initializes "this" as a client sasl session.
- *
- * May only be called once on an instance of GsaslSession, and may not be called on an
- * instance on which initializeServerSession has been called.
- *
- * "gsasl" is a pointer to a Gsasl library context that will exist for the rest of
- * the lifetime of "this".
- *
- * "mechanism" is a SASL mechanism name.
- *
- * "sessionHook" is user-supplied data associated with this session. If is accessible in
- * the gsasl callback set on "gsasl" using gsasl_session_hook_get(). May be NULL. Owned
- * by caller, and must stay in scope as long as this object.
- *
- * Returns Status::OK() on success, some other status on errors.
- */
- Status initializeClientSession(Gsasl* gsasl,
- const StringData& mechanism,
- void* sessionHook);
-
- /**
- * Initializes "this" as a server sasl session.
- *
- * May only be called once on an instance of GsaslSession, and may not be called on an
- * instance on which initializeClientSession has been called.
- *
- * "gsasl" is a pointer to a Gsasl library context that will exist for the rest of
- * the lifetime of "this".
- *
- * "mechanism" is a SASL mechanism name.
- *
- * "sessionHook" is user-supplied data associated with this session. If is accessible in
- * the gsasl callback set on "gsasl" using gsasl_session_hook_get(). May be NULL. Owned
- * by caller, and must stay in scope as long as this object.
- *
- * Returns Status::OK() on success, some other status on errors.
- */
- Status initializeServerSession(Gsasl* gsasl,
- const StringData& mechanism,
- void* sessionHook);
-
- /**
- * Returns the string name of the SASL mechanism in use in this session.
- *
- * Not valid before initializeServerSession() or initializeClientSession().
- */
- std::string getMechanism() const;
-
- /**
- * Sets a property on this session.
- *
- * Not valid before initializeServerSession() or initializeClientSession().
- */
- void setProperty(Gsasl_property property, const StringData& value);
-
- /**
- * Gets a property on this session. Return an empty string if the property isn't set.
- *
- * Not valid before initializeServerSession() or initializeClientSession().
- */
- const std::string getProperty(Gsasl_property property) const;
-
- /**
- * Performs one more step on this session.
- *
- * Receives "inputData" from the other side and produces "*outputData" to send.
- *
- * Both "inputData" and "*outputData" are logically strings of bytes, not characters.
- *
- * For the first step by the authentication initiator, "inputData" should have 0 length.
- *
- * Returns Status::OK() on success. In that case, isDone() can be queried to see if the
- * session expects another call to step(). If isDone() is true, the authentication has
- * completed successfully.
- *
- * Any return other than Status::OK() means that authentication has failed, but the specific
- * code or reason message may provide insight as to why.
- */
- Status step(const StringData& inputData, std::string* outputData);
-
- /**
- * Returns true if this session has completed successfully.
- *
- * That is, returns true if the session expects no more calls to step(), and all previous
- * calls to step() and initializeClientSession()/initializeServerSession() have returned
- * Status::OK().
- */
- bool isDone() const { return _done; }
-
- private:
- // Signature of gsas session start functions.
- typedef int (*GsaslSessionStartFn)(Gsasl*, const char*, Gsasl_session**);
-
- /**
- * Common helper code for initializing a session.
- *
- * Uses "sessionStartFn" to initialize the underlying Gsasl_session.
- */
- Status _initializeSession(GsaslSessionStartFn sessionStartFn,
- Gsasl* gsasl, const StringData& mechanism, void* sessionHook);
-
- /// Underlying C-library gsasl session object.
- Gsasl_session* _gsaslSession;
-
- /// See isDone(), above.
- bool _done;
- };
-
-} // namespace mongo