diff options
-rw-r--r-- | SConstruct | 2 | ||||
-rwxr-xr-x | distsrc/client/SConstruct | 2 | ||||
-rw-r--r-- | src/SConscript.client | 2 | ||||
-rw-r--r-- | src/mongo/SConscript | 4 | ||||
-rw-r--r-- | src/mongo/client/dbclient.cpp | 2 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate.cpp | 3 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate.h | 14 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate_impl.cpp | 280 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_session.cpp | 213 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_session.h | 152 | ||||
-rw-r--r-- | src/mongo/util/gsasl_session.cpp | 101 | ||||
-rw-r--r-- | src/mongo/util/gsasl_session.h | 147 |
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 |