summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorAndy Schwerin <schwerin@10gen.com>2012-10-29 13:12:36 -0400
committerAndy Schwerin <schwerin@10gen.com>2012-11-08 14:42:05 -0500
commit924afa46297540f1b1e787a10d70259413524fc0 (patch)
treeb6e0eb74ea4ca9ec905584c70d4a94ffe78854dc /src/mongo
parent444675bbd5a01a2149a4f112ba966ae85e3deed4 (diff)
downloadmongo-924afa46297540f1b1e787a10d70259413524fc0.tar.gz
Client and common support for SASL authentication.
SERVER-7130, SERVER-7131, SERVER-7133
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/SConscript10
-rw-r--r--src/mongo/base/error_codes.err8
-rw-r--r--src/mongo/client/sasl_client_authenticate.cpp229
-rw-r--r--src/mongo/client/sasl_client_authenticate.h129
-rw-r--r--src/mongo/util/gsasl_session.cpp94
-rw-r--r--src/mongo/util/gsasl_session.h138
6 files changed, 605 insertions, 3 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 829580a3c15..d60c3d017f4 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -126,6 +126,13 @@ commonFiles = [ "pch.cpp",
"db/dbmessage.cpp"
]
+commonSysLibdeps = []
+
+if env['MONGO_BUILD_SASL_CLIENT']:
+ commonFiles.extend(['client/sasl_client_authenticate.cpp',
+ 'util/gsasl_session.cpp'])
+ commonSysLibdeps.append('gsasl')
+
# handle processinfo*
processInfoFiles = [ "util/processinfo.cpp" ]
@@ -161,7 +168,8 @@ env.StaticLibrary('mongocommon', commonFiles,
'fail_point',
'$BUILD_DIR/third_party/pcrecpp',
'$BUILD_DIR/third_party/murmurhash3/murmurhash3',
- '$BUILD_DIR/third_party/shim_boost'],)
+ '$BUILD_DIR/third_party/shim_boost'],
+ SYSLIBDEPS=commonSysLibdeps)
env.StaticLibrary("coredb", [
"client/parallel.cpp",
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err
index 46956999cc1..59927f7a94e 100644
--- a/src/mongo/base/error_codes.err
+++ b/src/mongo/base/error_codes.err
@@ -16,7 +16,11 @@ error_code("UnsupportedFormat", 12)
error_code("Unauthorized", 13)
error_code("TypeMismatch", 14)
error_code("Overflow", 15)
-error_code("IllegalOperation", 16)
-error_code("EmptyArrayOperation", 17)
+error_code("InvalidLength", 16)
+error_code("ProtocolError", 17)
+error_code("AuthenticationFailed", 18)
+error_code("CannotReuseObject", 19)
+error_code("IllegalOperation", 20)
+error_code("EmptyArrayOperation", 21)
error_class("NetworkError", ["HostUnreachable", "HostNotFound"])
diff --git a/src/mongo/client/sasl_client_authenticate.cpp b/src/mongo/client/sasl_client_authenticate.cpp
new file mode 100644
index 00000000000..fe1e31add53
--- /dev/null
+++ b/src/mongo/client/sasl_client_authenticate.cpp
@@ -0,0 +1,229 @@
+/* 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_authenticate.h"
+
+#include <string>
+
+#include "mongo/base/string_data.h"
+#include "mongo/bson/util/bson_extract.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/net/hostandport.h"
+
+namespace mongo {
+
+ using namespace mongoutils;
+
+ const char* const saslStartCommandName = "saslStart";
+ const char* const saslContinueCommandName = "saslContinue";
+ const char* const saslCommandAutoAuthorizeFieldName = "autoAuthorize";
+ const char* const saslCommandCodeFieldName = "code";
+ const char* const saslCommandConversationIdFieldName = "conversationId";
+ const char* const saslCommandDoneFieldName = "done";
+ const char* const saslCommandErrmsgFieldName = "errmsg";
+ const char* const saslCommandMechanismFieldName = "mechanism";
+ const char* const saslCommandMechanismListFieldName = "supportedMechanisms";
+ const char* const saslCommandPasswordFieldName = "password";
+ const char* const saslCommandPayloadFieldName = "payload";
+ const char* const saslCommandPrincipalFieldName = "principal";
+ const char* const saslCommandServiceHostnameFieldName = "serviceHostname";
+ const char* const saslCommandServiceNameFieldName = "serviceName";
+ const char* const saslDefaultDBName = "admin";
+ const char* const saslDefaultServiceName = "mongodb";
+
+ const char* const saslClientLogFieldName = "clientLogLevel";
+
+namespace {
+ // Default log level on the client for SASL log messages.
+ const int defaultSaslClientLogLevel = 4;
+} // namespace
+
+ Status saslExtractPayload(const BSONObj& cmdObj, std::string* payload, BSONType* type) {
+ BSONElement payloadElement;
+ Status status = bsonExtractField(cmdObj, saslCommandPayloadFieldName, &payloadElement);
+ if (!status.isOK())
+ return status;
+
+ *type = payloadElement.type();
+ if (payloadElement.type() == BinData) {
+ const char* payloadData;
+ int payloadLen;
+ payloadData = payloadElement.binData(payloadLen);
+ if (payloadLen < 0)
+ return Status(ErrorCodes::InvalidLength, "Negative payload length");
+ *payload = std::string(payloadData, payloadData + payloadLen);
+ }
+ else if (payloadElement.type() == String) {
+ try {
+ *payload = base64::decode(payloadElement.str());
+ } catch (UserException& e) {
+ return Status(ErrorCodes::FailedToParse, e.what());
+ }
+ }
+ else {
+ return Status(ErrorCodes::TypeMismatch,
+ (str::stream() << "Wrong type for field; expected BinData or String for "
+ << payloadElement));
+ }
+
+ return Status::OK();
+ }
+
+namespace {
+
+ /**
+ * 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.
+ */
+ Status configureSession(Gsasl* gsasl,
+ DBClientWithCommands* client,
+ const BSONObj& saslParameters,
+ void* sessionHook,
+ GsaslSession* session) {
+
+ std::string mechanism;
+ Status status = bsonExtractStringField(saslParameters,
+ saslCommandMechanismFieldName,
+ &mechanism);
+ if (!status.isOK())
+ return status;
+
+ status = session->initializeClientSession(gsasl, mechanism, sessionHook);
+ if (!status.isOK())
+ return status;
+
+ std::string service;
+ status = bsonExtractStringFieldWithDefault(saslParameters,
+ saslCommandServiceNameFieldName,
+ saslDefaultServiceName,
+ &service);
+ if (!status.isOK())
+ return status;
+ session->setProperty(GSASL_SERVICE, service);
+
+ std::string hostname;
+ status = bsonExtractStringFieldWithDefault(saslParameters,
+ saslCommandServiceHostnameFieldName,
+ HostAndPort(client->getServerAddress()).host(),
+ &hostname);
+ if (!status.isOK())
+ return status;
+ session->setProperty(GSASL_HOSTNAME, hostname);
+
+ BSONElement element = saslParameters[saslCommandPrincipalFieldName];
+ if (element.type() == String) {
+ session->setProperty(GSASL_AUTHID, element.str());
+ }
+ else if (!element.eoo()) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "Expected string for " << element);
+ }
+
+ element = saslParameters[saslCommandPasswordFieldName];
+ if (element.type() == String) {
+ session->setProperty(GSASL_PASSWORD, element.str());
+ }
+ else if (!element.eoo()) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "Expected string for " << element);
+ }
+
+ 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;
+ }
+
+} // namespace
+
+ Status saslClientAuthenticate(Gsasl *gsasl,
+ DBClientWithCommands* client,
+ const BSONObj& saslParameters,
+ void* sessionHook) {
+
+ GsaslSession session;
+
+ int saslLogLevel = getSaslClientLogLevel(saslParameters);
+
+ Status status = configureSession(gsasl, client, saslParameters, sessionHook, &session);
+ if (!status.isOK())
+ return status;
+
+ BSONObj saslFirstCommandPrefix = BSON(
+ saslStartCommandName << 1 <<
+ saslCommandMechanismFieldName << session.getMechanism());
+
+ BSONObj saslFollowupCommandPrefix = BSON(saslContinueCommandName << 1);
+ BSONObj saslCommandPrefix = saslFirstCommandPrefix;
+ BSONObj inputObj = BSON(saslCommandPayloadFieldName << "");
+ bool isServerDone = false;
+ while (!session.isDone()) {
+ std::string payload;
+ BSONType type;
+
+ status = saslExtractPayload(inputObj, &payload, &type);
+ if (!status.isOK())
+ return status;
+
+ LOG(saslLogLevel) << "sasl client input: " << base64::encode(payload) << endl;
+
+ std::string responsePayload;
+ status = session.step(payload, &responsePayload);
+ if (!status.isOK())
+ return status;
+
+ LOG(saslLogLevel) << "sasl client output: " << base64::encode(responsePayload) << endl;
+
+ BSONObjBuilder commandBuilder;
+ commandBuilder.appendElements(saslCommandPrefix);
+ commandBuilder.appendBinData(saslCommandPayloadFieldName,
+ int(responsePayload.size()),
+ BinDataGeneral,
+ responsePayload.c_str());
+ BSONElement conversationId = inputObj[saslCommandConversationIdFieldName];
+ if (!conversationId.eoo())
+ commandBuilder.append(conversationId);
+
+ if (!client->runCommand(saslDefaultDBName, commandBuilder.obj(), inputObj)) {
+ return Status(ErrorCodes::UnknownError,
+ inputObj[saslCommandErrmsgFieldName].str());
+ }
+
+ int statusCodeInt = inputObj[saslCommandCodeFieldName].Int();
+ if (0 != statusCodeInt)
+ return Status(ErrorCodes::fromInt(statusCodeInt),
+ inputObj[saslCommandErrmsgFieldName].str());
+
+ isServerDone = inputObj[saslCommandDoneFieldName].trueValue();
+ saslCommandPrefix = saslFollowupCommandPrefix;
+ }
+
+ if (!isServerDone)
+ return Status(ErrorCodes::ProtocolError, "Client finished before server.");
+ return Status::OK();
+ }
+
+} // namespace mongo
diff --git a/src/mongo/client/sasl_client_authenticate.h b/src/mongo/client/sasl_client_authenticate.h
new file mode 100644
index 00000000000..19de215aa14
--- /dev/null
+++ b/src/mongo/client/sasl_client_authenticate.h
@@ -0,0 +1,129 @@
+/* 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 "mongo/base/status.h"
+#include "mongo/bson/bsontypes.h"
+#include "mongo/client/dbclientinterface.h"
+
+struct Gsasl;
+
+namespace mongo {
+ class BSONObj;
+
+ /**
+ * Attempts to authenticate "client" using the SASL protocol.
+ *
+ * Requires an initialized instance of the "gsasl" library, as the first
+ * parameter.
+ *
+ * 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.
+ *
+ * "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.
+ * "database": The database target of the auth command. Optional for GSSAPI/Kerberos.
+ * "principal": The string name of the principal to authenticate, GSASL_AUTHID.
+ * "password": The password data, GSASL_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.
+ */
+ Status saslClientAuthenticate(Gsasl *gsasl,
+ DBClientWithCommands* client,
+ const BSONObj& saslParameters,
+ void* sessionHook);
+
+ /**
+ * Extracts the payload field from "cmdObj", and store it into "*payload".
+ *
+ * Sets "*type" to the BSONType of the payload field in cmdObj.
+ *
+ * If the type of the payload field is String, the contents base64 decodes and
+ * stores into "*payload". If the type is BinData, the contents are stored directly
+ * into "*payload". In all other cases, returns
+ */
+ Status saslExtractPayload(const BSONObj& cmdObj, std::string* payload, BSONType* type);
+
+ // Constants
+
+ /// String name of the saslStart command.
+ extern const char* const saslStartCommandName;
+
+ /// String name of the saslContinue command.
+ extern const char* const saslContinueCommandName;
+
+ /// Name of the saslStart parameter indicating that the server should automatically grant the
+ /// connection all privileges associated with the principal after successful authentication.
+ extern const char* const saslCommandAutoAuthorizeFieldName;
+
+ /// Name of the field contain the status code in responses from the server.
+ extern const char* const saslCommandCodeFieldName;
+
+ /// Name of the field containing the conversation identifier in server respones and saslContinue
+ /// commands.
+ extern const char* const saslCommandConversationIdFieldName;
+
+ /// Name of the field that indicates whether or not the server believes authentication has
+ /// completed successfully.
+ extern const char* const saslCommandDoneFieldName;
+
+ /// Field in which to store error messages associated with non-success return codes.
+ extern const char* const saslCommandErrmsgFieldName;
+
+ /// Name of parameter to saslStart command indiciating the client's desired sasl mechanism.
+ extern const char* const saslCommandMechanismFieldName;
+
+ /// In the event that saslStart supplies an unsupported mechanism, the server responds with a
+ /// field by this name, with a list of supported mechanisms.
+ extern const char* const saslCommandMechanismListFieldName;
+
+ /// Field containing password information for saslClientAuthenticate().
+ extern const char* const saslCommandPasswordFieldName;
+
+ /// Field containing sasl payloads passed to and from the server.
+ extern const char* const saslCommandPayloadFieldName;
+
+ /// Field containing the string identifier of the principal to authenticate in
+ /// saslClientAuthenticate().
+ extern const char* const saslCommandPrincipalFieldName;
+
+ /// Field overriding the FQDN of the hostname hosting the mongodb srevice in
+ /// saslClientAuthenticate().
+ extern const char* const saslCommandServiceHostnameFieldName;
+
+ /// Field overriding the name of the mongodb service saslClientAuthenticate().
+ extern const char* const saslCommandServiceNameFieldName;
+
+ /// Default database against which sasl authentication commands should run.
+ extern const char* const saslDefaultDBName;
+
+ /// Default sasl service name, "mongodb".
+ extern const char* const saslDefaultServiceName;
+}
diff --git a/src/mongo/util/gsasl_session.cpp b/src/mongo/util/gsasl_session.cpp
new file mode 100644
index 00000000000..c7ed0a4a8ec
--- /dev/null
+++ b/src/mongo/util/gsasl_session.cpp
@@ -0,0 +1,94 @@
+/* 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 <gsasl.h>
+
+#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.data(), value.size());
+ }
+
+ 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.data(), &_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.data(), 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
new file mode 100644
index 00000000000..f78f723f460
--- /dev/null
+++ b/src/mongo/util/gsasl_session.h
@@ -0,0 +1,138 @@
+/* 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 <gsasl.h>
+#include <string>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/base/status.h"
+#include "mongo/base/string_data.h"
+
+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);
+
+ /**
+ * 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