5555#include < algorithm>
5656#include < cstdlib>
5757
58+ #ifdef MONGO_SSL
59+ #include " mongo/client/native_sasl_client_session.h"
60+ #endif
61+
5862namespace mongo {
5963
6064 using std::auto_ptr;
@@ -149,6 +153,10 @@ namespace mongo {
149153 const char kAuthServiceName [] = " SERVICE_NAME" ;
150154 const char kAuthServiceRealm [] = " SERVICE_REALM" ;
151155
156+ const char kAuthMechMongoCR [] = " MONGODB-CR" ;
157+ const char kAuthMechScramSha1 [] = " SCRAM-SHA-1" ;
158+ const char kAuthMechDefault [] = " DEFAULT" ;
159+
152160 const char * const kSupportedAuthMechanismProperties [] = {
153161 kAuthServiceName ,
154162 kAuthServiceRealm
@@ -180,6 +188,24 @@ namespace mongo {
180188 }
181189 return bob.obj ();
182190 }
191+
192+ string authKeyCopyDBMongoCR (const string& username,
193+ const string& password,
194+ const string& nonce)
195+ {
196+ md5digest d;
197+ string passwordDigest = createPasswordDigest ( username, password );
198+ {
199+ md5_state_t st;
200+ md5_init (&st);
201+ md5_append (&st, reinterpret_cast <const md5_byte_t *>(nonce.c_str ()), nonce.size () );
202+ md5_append (&st, reinterpret_cast <const md5_byte_t *>(username.data ()), username.length ());
203+ md5_append (&st, reinterpret_cast <const md5_byte_t *>(passwordDigest.c_str ()), passwordDigest.size () );
204+ md5_finish (&st, d);
205+ }
206+ return digestToString ( d );
207+ }
208+
183209 } // namespace
184210
185211 BSONObj ConnectionString::_makeAuthObjFromOptions (int maxWireVersion) const {
@@ -205,9 +231,9 @@ namespace mongo {
205231 if (!elt.eoo ()) {
206232 bob.appendAs (elt, saslCommandMechanismFieldName);
207233 } else if (maxWireVersion >= 3 ) {
208- bob.append (saslCommandMechanismFieldName, " SCRAM-SHA-1 " );
234+ bob.append (saslCommandMechanismFieldName, kAuthMechScramSha1 );
209235 } else {
210- bob.append (saslCommandMechanismFieldName, " MONGODB-CR " );
236+ bob.append (saslCommandMechanismFieldName, kAuthMechMongoCR );
211237 }
212238
213239 elt = _options.getField (" authMechanismProperties" );
@@ -705,7 +731,7 @@ namespace mongo {
705731 BSONObjBuilder cmdObj;
706732 cmdObj.appendElements (cmd);
707733 _runCommandHook (&cmdObj);
708-
734+
709735 info = findOne (ns, cmdObj.done (), 0 , options);
710736 }
711737 else {
@@ -848,7 +874,7 @@ namespace mongo {
848874 !(params.hasField (saslCommandUserDBFieldName)
849875 && params.hasField (saslCommandUserSourceFieldName)));
850876
851- if (mechanism == StringData ( " MONGODB-CR " , StringData::LiteralTag ()) ) {
877+ if (mechanism == kAuthMechMongoCR ) {
852878 std::string db;
853879 if (params.hasField (saslCommandUserSourceFieldName)) {
854880 uassertStatusOK (bsonExtractStringField (params,
@@ -942,9 +968,9 @@ namespace mongo {
942968 string& errmsg,
943969 bool digestPassword) {
944970 try {
945- const char * mech = " MONGODB-CR " ;
971+ const char * mech = kAuthMechMongoCR ;
946972 if ( _maxWireVersion >= 3 ) {
947- mech = " SCRAM-SHA-1 " ;
973+ mech = kAuthMechScramSha1 ;
948974 }
949975 _auth (BSON (saslCommandMechanismFieldName << mech <<
950976 saslCommandUserDBFieldName << dbname <<
@@ -1066,15 +1092,153 @@ namespace mongo {
10661092 return runCommand (db.c_str (), b.done (), *info);
10671093 }
10681094
1069- bool DBClientWithCommands::copyDatabase (const string &fromdb, const string &todb, const string &fromhost, BSONObj *info) {
1095+ bool DBClientWithCommands::copyDatabase (const string& fromdb,
1096+ const string& todb,
1097+ const string& fromhost,
1098+ const string& mechanism,
1099+ const string& username,
1100+ const string& password,
1101+ BSONObj *info) {
10701102 BSONObj o;
10711103 if ( info == 0 ) info = &o;
1072- BSONObjBuilder b;
1073- b.append (" copydb" , 1 );
1074- b.append (" fromhost" , fromhost);
1075- b.append (" fromdb" , fromdb);
1076- b.append (" todb" , todb);
1077- return runCommand (" admin" , b.done (), *info);
1104+ BSONObjBuilder copydbCmd;
1105+ copydbCmd.append (" copydb" , 1 );
1106+ copydbCmd.append (" fromhost" , fromhost);
1107+ copydbCmd.append (" fromdb" , fromdb);
1108+ copydbCmd.append (" todb" , todb);
1109+
1110+ // If we don't have a username, or if we're copying locally,
1111+ // just run the command without authenticating
1112+ if ( username == " " || fromhost == " " ) {
1113+ return runCommand (" admin" , copydbCmd.done (), *info);
1114+ }
1115+
1116+ // Otherwise, take or guess the auth mechanism
1117+ string authMech;
1118+ if ( mechanism != kAuthMechDefault ) {
1119+ uassert ( 0 , " auth mechanism must be MONGODB-CR or SCRAM-SHA-1" ,
1120+ ( mechanism == kAuthMechMongoCR ||
1121+ mechanism == kAuthMechScramSha1 ));
1122+ authMech = mechanism;
1123+ }
1124+ #ifdef MONGO_SSL
1125+ else if ( (static_cast <DBClientBase*>(this ))->getMaxWireVersion () >= 3 ) {
1126+ authMech = kAuthMechScramSha1 ;
1127+ }
1128+ #endif
1129+ else {
1130+ authMech = kAuthMechMongoCR ;
1131+ }
1132+
1133+ if ( authMech == kAuthMechMongoCR ) {
1134+ // run MONGODB-CR copydb
1135+ BSONObj nonceInfo;
1136+ BSONObjBuilder nonceCmd;
1137+ BSONElement e;
1138+ string nonce;
1139+
1140+ nonceCmd.append (" copydbgetnonce" , 1 );
1141+ nonceCmd.append (" fromhost" , fromhost);
1142+
1143+ verify ( runCommand (" admin" , nonceCmd.done (), nonceInfo) );
1144+ {
1145+ BSONElement e = nonceInfo.getField (" nonce" );
1146+ verify ( e.type () == String );
1147+ nonce = e.valuestr ();
1148+ }
1149+
1150+ copydbCmd.append (" username" , username);
1151+ copydbCmd.append (" nonce" , nonce);
1152+ copydbCmd.append (" key" , authKeyCopyDBMongoCR (username, password, nonce));
1153+ return runCommand (" admin" , copydbCmd.done (), *info);
1154+ }
1155+ else {
1156+ // run SCRAM-SHA-1 copydb, but only with SSL
1157+ #ifndef MONGO_SSL
1158+ uassert ( 0 , " SCRAM-SHA-1 authentication requires driver to be built with SSL" , false );
1159+ #endif
1160+ #ifdef MONGO_SSL
1161+ string hashedPwd = createPasswordDigest (username, password);
1162+
1163+ // create and initialize our sasl session
1164+ boost::scoped_ptr<SaslClientSession> session (new NativeSaslClientSession ());
1165+ session->setParameter (SaslClientSession::parameterMechanism, kAuthMechScramSha1 );
1166+ session->setParameter (SaslClientSession::parameterUser, username);
1167+ session->setParameter (SaslClientSession::parameterPassword, hashedPwd);
1168+ session->initialize ();
1169+
1170+ // set up commands to feed the sasl state machine
1171+ BSONObj saslFirstCommandPrefix = BSON (" copydbsaslstart" << 1 <<
1172+ " fromhost" << fromhost <<
1173+ " fromdb" << fromdb <<
1174+ saslCommandMechanismFieldName << kAuthMechScramSha1 );
1175+
1176+ BSONObj saslFollowupCommandPrefix = BSON (" copydb" << 1 <<
1177+ " fromhost" << fromhost <<
1178+ " fromdb" << fromdb <<
1179+ " todb" << todb);
1180+
1181+ BSONObj saslCommandPrefix = saslFirstCommandPrefix;
1182+ BSONObj inputObj = BSON (saslCommandPayloadFieldName << " " );
1183+ bool isServerDone = false ;
1184+
1185+ // send copydbsaslstart, then continue to send copydb until we are done.
1186+ while (!session->isDone ()) {
1187+ string payload;
1188+ BSONType type;
1189+
1190+ Status status = saslExtractPayload (inputObj, &payload, &type);
1191+ if (!status.isOK ()) {
1192+ throw DBException ( str::stream () << " sasl session failure: " << status.reason (), 0 );
1193+ }
1194+
1195+ string responsePayload;
1196+ status = session->step (payload, &responsePayload);
1197+ if (!status.isOK ()) {
1198+ throw DBException ( str::stream () << " sasl session failure: " << status.reason (), 0 );
1199+ }
1200+
1201+ // build command to send to server
1202+ BSONObjBuilder commandBuilder;
1203+ commandBuilder.appendElements (saslCommandPrefix);
1204+ commandBuilder.appendBinData (saslCommandPayloadFieldName,
1205+ int (responsePayload.size ()),
1206+ BinDataGeneral,
1207+ responsePayload.c_str ());
1208+ BSONElement conversationId = inputObj[saslCommandConversationIdFieldName];
1209+ if (!conversationId.eoo ()) {
1210+ commandBuilder.append (conversationId);
1211+ }
1212+
1213+ BSONObj command = commandBuilder.obj ();
1214+ bool ok = runCommand (" admin" , command, inputObj);
1215+
1216+ ErrorCodes::Error code =
1217+ ErrorCodes::fromInt (inputObj[saslCommandCodeFieldName].numberInt ());
1218+
1219+ if (!ok || code != ErrorCodes::OK) {
1220+ if (code == ErrorCodes::OK)
1221+ code = ErrorCodes::UnknownError;
1222+ // attempt to give a sane error message
1223+ if ( inputObj.hasField (" errmsg" ) ) {
1224+ BSONElement e = inputObj.getField (" errmsg" );
1225+ uassert ( 0 , " fromhost " + fromhost + " doesn't support SCRAM-SHA-1, use MONGODB-CR" ,
1226+ ( e.type () == String &&
1227+ strstr ( e.valuestr (), " no such cmd: saslStart" )));
1228+ }
1229+ throw OperationException ( inputObj );
1230+ }
1231+
1232+ isServerDone = inputObj[saslCommandDoneFieldName].trueValue ();
1233+ saslCommandPrefix = saslFollowupCommandPrefix;
1234+ }
1235+ if (!isServerDone) {
1236+ invariant (false );
1237+ }
1238+ return true ;
1239+ #endif /* MONGO_SSL */
1240+ }
1241+ invariant (false );
10781242 }
10791243
10801244 bool DBClientWithCommands::setDbProfilingLevel (const string &dbname, ProfilingLevel level, BSONObj *info ) {
@@ -1581,7 +1745,7 @@ namespace mongo {
15811745 int options) {
15821746 if (DBClientWithCommands::runCommand (dbname, cmd, info, options))
15831747 return true ;
1584-
1748+
15851749 if ( clientSet && isNotMasterErrorString ( info[" errmsg" ] ) ) {
15861750 clientSet->isntMaster ();
15871751 }
0 commit comments