/* * Parse client-first-message of the form: * n,a=authzid,n=encoded-username,r=client-nonce * * Generate server-first-message on the form: * r=client-nonce|server-nonce,s=user-salt,i=iteration-count * * NOTE: we are ignoring the authorization ID part of the message */ StatusWith<bool> SaslSCRAMSHA1ServerConversation::_firstStep(std::vector<string>& input, std::string* outputData) { std::string authzId = ""; if (input.size() == 4) { /* The second entry a=authzid is optional. If provided it will be * validated against the encoded username. * * The two allowed input forms are: * n,,n=encoded-username,r=client-nonce * n,a=authzid,n=encoded-username,r=client-nonce */ if (!str::startsWith(input[1], "a=") || input[1].size() < 3) { return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << "Incorrect SCRAM-SHA-1 authzid: " << input[1]); } authzId = input[1].substr(2); input.erase(input.begin() + 1); } if (input.size() != 3) { return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << "Incorrect number of arguments for first SCRAM-SHA-1 client message, got " << input.size() << " expected 4"); } else if (input[0] != "n") { return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << "Incorrect SCRAM-SHA-1 client message prefix: " << input[0]); } else if (!str::startsWith(input[1], "n=") || input[1].size() < 3) { return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << "Incorrect SCRAM-SHA-1 user name: " << input[1]); } else if(!str::startsWith(input[2], "r=") || input[2].size() < 6) { return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << "Incorrect SCRAM-SHA-1 client nonce: " << input[2]); } _user = input[1].substr(2); if (!authzId.empty() && _user != authzId) { return StatusWith<bool>(ErrorCodes::BadValue, mongoutils::str::stream() << "SCRAM-SHA-1 user name " << _user << " does not match authzid " << authzId); } decodeSCRAMUsername(_user); // SERVER-16534, SCRAM-SHA-1 must be enabled for authenticating the internal user, so that // cluster members may communicate with each other. Hence ignore disabled auth mechanism // for the internal user. UserName user(_user, _saslAuthSession->getAuthenticationDatabase()); if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1") && user != internalSecurity.user->getName()) { return StatusWith<bool>(ErrorCodes::BadValue, "SCRAM-SHA-1 authentication is disabled"); } // add client-first-message-bare to _authMessage _authMessage += input[1] + "," + input[2] + ","; std::string clientNonce = input[2].substr(2); // The authentication database is also the source database for the user. User* userObj; Status status = _saslAuthSession->getAuthorizationSession()->getAuthorizationManager(). acquireUser(_saslAuthSession->getOpCtxt(), user, &userObj); if (!status.isOK()) { return StatusWith<bool>(status); } _creds = userObj->getCredentials(); UserName userName = userObj->getName(); _saslAuthSession->getAuthorizationSession()->getAuthorizationManager(). releaseUser(userObj); // Check for authentication attempts of the __system user on // systems started without a keyfile. if (userName == internalSecurity.user->getName() && _creds.scram.salt.empty()) { return StatusWith<bool>(ErrorCodes::AuthenticationFailed, "It is not possible to authenticate as the __system user " "on servers started without a --keyFile parameter"); } // Generate SCRAM credentials on the fly for mixed MONGODB-CR/SCRAM mode. if (_creds.scram.salt.empty() && !_creds.password.empty()) { // Use a default value of 5000 for the scramIterationCount when in mixed mode, // overriding the default value (10000) used for SCRAM mode or the user-given value. const int mixedModeScramIterationCount = 5000; BSONObj scramCreds = scram::generateCredentials(_creds.password, mixedModeScramIterationCount); _creds.scram.iterationCount = scramCreds[scram::iterationCountFieldName].Int(); _creds.scram.salt = scramCreds[scram::saltFieldName].String(); _creds.scram.storedKey = scramCreds[scram::storedKeyFieldName].String(); _creds.scram.serverKey = scramCreds[scram::serverKeyFieldName].String(); } // Generate server-first-message // Create text-based nonce as base64 encoding of a binary blob of length multiple of 3 const int nonceLenQWords = 3; uint64_t binaryNonce[nonceLenQWords]; scoped_ptr<SecureRandom> sr(SecureRandom::create()); binaryNonce[0] = sr->nextInt64(); binaryNonce[1] = sr->nextInt64(); binaryNonce[2] = sr->nextInt64(); _nonce = clientNonce + base64::encode(reinterpret_cast<char*>(binaryNonce), sizeof(binaryNonce)); StringBuilder sb; sb << "r=" << _nonce << ",s=" << _creds.scram.salt << ",i=" << _creds.scram.iterationCount; *outputData = sb.str(); // add server-first-message to authMessage _authMessage += *outputData + ","; return StatusWith<bool>(false); }
Status enforceLegacyWriteConcern( MultiCommandDispatch* dispatcher, const StringData& dbName, const BSONObj& options, const HostOpTimeMap& hostOpTimes, vector<LegacyWCResponse>* legacyWCResponses ) { if ( hostOpTimes.empty() ) { return Status::OK(); } for ( HostOpTimeMap::const_iterator it = hostOpTimes.begin(); it != hostOpTimes.end(); ++it ) { const ConnectionString& shardEndpoint = it->first; const HostOpTime hot = it->second; const OpTime& opTime = hot.opTime; const OID& electionId = hot.electionId; LOG( 3 ) << "enforcing write concern " << options << " on " << shardEndpoint.toString() << " at opTime " << opTime.toStringPretty() << " with electionID " << electionId; BSONObj gleCmd = buildGLECmdWithOpTime( options, opTime, electionId ); RawBSONSerializable gleCmdSerial( gleCmd ); dispatcher->addCommand( shardEndpoint, dbName, gleCmdSerial ); } dispatcher->sendAll(); vector<Status> failedStatuses; while ( dispatcher->numPending() > 0 ) { ConnectionString shardEndpoint; RawBSONSerializable gleResponseSerial; Status dispatchStatus = dispatcher->recvAny( &shardEndpoint, &gleResponseSerial ); if ( !dispatchStatus.isOK() ) { // We need to get all responses before returning failedStatuses.push_back( dispatchStatus ); continue; } BSONObj gleResponse = BatchSafeWriter::stripNonWCInfo( gleResponseSerial.toBSON() ); // Use the downconversion tools to determine if this GLE response is ok, a // write concern error, or an unknown error we should immediately abort for. BatchSafeWriter::GLEErrors errors; Status extractStatus = BatchSafeWriter::extractGLEErrors( gleResponse, &errors ); if ( !extractStatus.isOK() ) { failedStatuses.push_back( extractStatus ); continue; } LegacyWCResponse wcResponse; wcResponse.shardHost = shardEndpoint.toString(); wcResponse.gleResponse = gleResponse; if ( errors.wcError.get() ) { wcResponse.errToReport = errors.wcError->getErrMessage(); } legacyWCResponses->push_back( wcResponse ); } if ( failedStatuses.empty() ) { return Status::OK(); } StringBuilder builder; builder << "could not enforce write concern"; for ( vector<Status>::const_iterator it = failedStatuses.begin(); it != failedStatuses.end(); ++it ) { const Status& failedStatus = *it; if ( it == failedStatuses.begin() ) { builder << causedBy( failedStatus.toString() ); } else { builder << ":: and ::" << failedStatus.toString(); } } return Status( failedStatuses.size() == 1u ? failedStatuses.front().code() : ErrorCodes::MultipleErrorsOccurred, builder.str() ); }
/** * Edit a variable or input buffer text in an external editor -- EDITOR must be defined * * @param whatToEdit Name of JavaScript variable to be edited, or any text string */ static void edit( const string& whatToEdit ) { // EDITOR may be defined in the JavaScript scope or in the environment string editor; if ( shellMainScope->type( "EDITOR" ) == String ) { editor = shellMainScope->getString( "EDITOR" ); } else { static const char * editorFromEnv = getenv( "EDITOR" ); if ( editorFromEnv ) { editor = editorFromEnv; } } if ( editor.empty() ) { cout << "please define EDITOR as a JavaScript string or as an environment variable" << endl; return; } // "whatToEdit" might look like a variable/property name bool editingVariable = true; for ( const char* p = whatToEdit.c_str(); *p; ++p ) { if ( ! ( isalnum( *p ) || *p == '_' || *p == '.' ) ) { editingVariable = false; break; } } string js; if ( editingVariable ) { // If "whatToEdit" is undeclared or uninitialized, declare int varType = shellMainScope->type( whatToEdit.c_str() ); if ( varType == Undefined ) { shellMainScope->exec( "var " + whatToEdit , "(shell)", false, true, false ); } // Convert "whatToEdit" to JavaScript (JSON) text if ( !shellMainScope->exec( "__jsout__ = tojson(" + whatToEdit + ")", "tojs", false, false, false ) ) return; // Error already printed js = shellMainScope->getString( "__jsout__" ); if ( strstr( js.c_str(), "[native code]" ) ) { cout << "can't edit native functions" << endl; return; } } else { js = whatToEdit; } // Pick a name to use for the temp file string filename; const int maxAttempts = 10; int i; for ( i = 0; i < maxAttempts; ++i ) { StringBuilder sb; #ifdef _WIN32 char tempFolder[MAX_PATH]; GetTempPathA( sizeof tempFolder, tempFolder ); sb << tempFolder << "mongo_edit" << time( 0 ) + i << ".js"; #else sb << "/tmp/mongo_edit" << time( 0 ) + i << ".js"; #endif filename = sb.str(); if ( ! fileExists( filename ) ) break; } if ( i == maxAttempts ) { cout << "couldn't create unique temp file after " << maxAttempts << " attempts" << endl; return; } // Create the temp file FILE * tempFileStream; tempFileStream = fopen( filename.c_str(), "wt" ); if ( ! tempFileStream ) { cout << "couldn't create temp file (" << filename << "): " << errnoWithDescription() << endl; return; } // Write JSON into the temp file size_t fileSize = js.size(); if ( fwrite( js.data(), sizeof( char ), fileSize, tempFileStream ) != fileSize ) { int systemErrno = errno; cout << "failed to write to temp file: " << errnoWithDescription( systemErrno ) << endl; fclose( tempFileStream ); remove( filename.c_str() ); return; } fclose( tempFileStream ); // Pass file to editor StringBuilder sb; sb << editor << " " << filename; int ret = ::system( sb.str().c_str() ); if ( ret ) { if ( ret == -1 ) { int systemErrno = errno; cout << "failed to launch $EDITOR (" << editor << "): " << errnoWithDescription( systemErrno ) << endl; } else cout << "editor exited with error (" << ret << "), not applying changes" << endl; remove( filename.c_str() ); return; } // The editor gave return code zero, so read the file back in tempFileStream = fopen( filename.c_str(), "rt" ); if ( ! tempFileStream ) { cout << "couldn't open temp file on return from editor: " << errnoWithDescription() << endl; remove( filename.c_str() ); return; } sb.reset(); int bytes; do { char buf[1024]; bytes = fread( buf, sizeof( char ), sizeof buf, tempFileStream ); if ( ferror( tempFileStream ) ) { cout << "failed to read temp file: " << errnoWithDescription() << endl; fclose( tempFileStream ); remove( filename.c_str() ); return; } sb.append( StringData( buf, bytes ) ); } while ( bytes ); // Done with temp file, close and delete it fclose( tempFileStream ); remove( filename.c_str() ); if ( editingVariable ) { // Try to execute assignment to copy edited value back into the variable const string code = whatToEdit + string( " = " ) + sb.str(); if ( !shellMainScope->exec( code, "tojs", false, true, false ) ) { cout << "error executing assignment: " << code << endl; } } else { linenoisePreloadBuffer( sb.str().c_str() ); } }
void BatchSafeWriter::safeWriteBatch( DBClientBase* conn, const BatchedCommandRequest& request, BatchedCommandResponse* response ) { const NamespaceString nss( request.getNS() ); // N starts at zero, and we add to it for each item response->setN( 0 ); // GLE path always sets nModified to -1 (sentinel) to indicate we should omit it later. response->setNModified(-1); for ( size_t i = 0; i < request.sizeWriteOps(); ++i ) { // Break on first error if we're ordered if ( request.getOrdered() && response->isErrDetailsSet() ) break; BatchItemRef itemRef( &request, static_cast<int>( i ) ); BSONObj gleResult; GLEErrors errors; Status status = _safeWriter->safeWrite( conn, itemRef, WriteConcernOptions::Acknowledged, &gleResult ); if ( status.isOK() ) { status = extractGLEErrors( gleResult, &errors ); } if ( !status.isOK() ) { response->clear(); response->setOk( false ); response->setErrCode( ErrorCodes::RemoteResultsUnavailable ); StringBuilder builder; builder << "could not get write error from safe write"; builder << causedBy( status.toString() ); response->setErrMessage( builder.str() ); return; } if ( errors.wcError.get() ) { response->setWriteConcernError( errors.wcError.release() ); } // // STATS HANDLING // GLEStats stats; extractGLEStats( gleResult, &stats ); // Special case for making legacy "n" field result for insert match the write // command result. if ( request.getBatchType() == BatchedCommandRequest::BatchType_Insert && !errors.writeError.get() ) { // n is always 0 for legacy inserts. dassert( stats.n == 0 ); stats.n = 1; } response->setN( response->getN() + stats.n ); if ( !stats.upsertedId.isEmpty() ) { BatchedUpsertDetail* upsertedId = new BatchedUpsertDetail; upsertedId->setIndex( i ); upsertedId->setUpsertedID( stats.upsertedId ); response->addToUpsertDetails( upsertedId ); } response->setLastOp( stats.lastOp ); // Save write error if ( errors.writeError.get() ) { errors.writeError->setIndex( i ); response->addToErrDetails( errors.writeError.release() ); } } // // WRITE CONCERN ERROR HANDLING // // The last write is weird, since we enforce write concern and check the error through // the same GLE if possible. If the last GLE was an error, the write concern may not // have been enforced in that same GLE, so we need to send another after resetting the // error. BSONObj writeConcern; if ( request.isWriteConcernSet() ) { writeConcern = request.getWriteConcern(); // Pre-2.4.2 mongods react badly to 'w' being set on config servers if ( nss.db() == "config" ) writeConcern = fixWCForConfig( writeConcern ); } bool needToEnforceWC = WriteConcernOptions::Acknowledged.woCompare(writeConcern) != 0 && WriteConcernOptions::Unacknowledged.woCompare(writeConcern) != 0; if ( needToEnforceWC && ( !response->isErrDetailsSet() || ( !request.getOrdered() && // Not all errored. Note: implicit response->isErrDetailsSet(). response->sizeErrDetails() < request.sizeWriteOps() ))) { // Might have gotten a write concern validity error earlier, these are // enforced even if the wc isn't applied, so we ignore. response->unsetWriteConcernError(); const string dbName( nss.db().toString() ); Status status( Status::OK() ); if ( response->isErrDetailsSet() ) { const WriteErrorDetail* lastError = response->getErrDetails().back(); // If last write op was an error. if ( lastError->getIndex() == static_cast<int>( request.sizeWriteOps() - 1 )) { // Reset previous errors so we can apply the write concern no matter what // as long as it is valid. status = _safeWriter->clearErrors( conn, dbName ); } } BSONObj gleResult; if ( status.isOK() ) { status = _safeWriter->enforceWriteConcern( conn, dbName, writeConcern, &gleResult ); } GLEErrors errors; if ( status.isOK() ) { status = extractGLEErrors( gleResult, &errors ); } if ( !status.isOK() ) { auto_ptr<WCErrorDetail> wcError( new WCErrorDetail ); wcError->setErrCode( status.code() ); wcError->setErrMessage( status.reason() ); response->setWriteConcernError( wcError.release() ); } else if ( errors.wcError.get() ) { response->setWriteConcernError( errors.wcError.release() ); } } response->setOk( true ); dassert( response->isValid( NULL ) ); }
DiskLoc SimpleRecordStoreV1::_allocFromExistingExtents( OperationContext* txn, int lenToAlloc ) { // align size up to a multiple of 4 lenToAlloc = (lenToAlloc + (4-1)) & ~(4-1); freelistAllocs.increment(); DiskLoc loc; { DiskLoc *prev = 0; DiskLoc *bestprev = 0; DiskLoc bestmatch; int bestmatchlen = INT_MAX; // sentinel meaning we haven't found a record big enough int b = bucket(lenToAlloc); DiskLoc cur = _details->deletedListEntry(b); int extra = 5; // look for a better fit, a little. int chain = 0; while ( 1 ) { { // defensive check int fileNumber = cur.a(); int fileOffset = cur.getOfs(); if (fileNumber < -1 || fileNumber >= 100000 || fileOffset < 0) { StringBuilder sb; sb << "Deleted record list corrupted in collection " << _ns << ", bucket " << b << ", link number " << chain << ", invalid link is " << cur.toString() << ", throwing Fatal Assertion"; log() << sb.str() << endl; fassertFailed(16469); } } if ( cur.isNull() ) { // move to next bucket. if we were doing "extra", just break if ( bestmatchlen < INT_MAX ) break; if ( chain > 0 ) { // if we looked at things in the right bucket, but they were not suitable freelistBucketExhausted.increment(); } b++; if ( b > MaxBucket ) { // out of space. alloc a new extent. freelistIterations.increment( 1 + chain ); return DiskLoc(); } cur = _details->deletedListEntry(b); prev = 0; continue; } DeletedRecord *r = drec(cur); if ( r->lengthWithHeaders() >= lenToAlloc && r->lengthWithHeaders() < bestmatchlen ) { bestmatchlen = r->lengthWithHeaders(); bestmatch = cur; bestprev = prev; if (r->lengthWithHeaders() == lenToAlloc) // exact match, stop searching break; } if ( bestmatchlen < INT_MAX && --extra <= 0 ) break; if ( ++chain > 30 && b <= MaxBucket ) { // too slow, force move to next bucket to grab a big chunk //b++; freelistIterations.increment( chain ); chain = 0; cur.Null(); } else { cur = r->nextDeleted(); prev = &r->nextDeleted(); } } // unlink ourself from the deleted list DeletedRecord *bmr = drec(bestmatch); if ( bestprev ) { *txn->recoveryUnit()->writing(bestprev) = bmr->nextDeleted(); } else { // should be the front of a free-list int myBucket = bucket(bmr->lengthWithHeaders()); invariant( _details->deletedListEntry(myBucket) == bestmatch ); _details->setDeletedListEntry(txn, myBucket, bmr->nextDeleted()); } *txn->recoveryUnit()->writing(&bmr->nextDeleted()) = DiskLoc().setInvalid(); // defensive. invariant(bmr->extentOfs() < bestmatch.getOfs()); freelistIterations.increment( 1 + chain ); loc = bestmatch; } if ( loc.isNull() ) return loc; // determine if we should chop up DeletedRecord *r = drec(loc); /* note we want to grab from the front so our next pointers on disk tend to go in a forward direction which is important for performance. */ int regionlen = r->lengthWithHeaders(); invariant( r->extentOfs() < loc.getOfs() ); int left = regionlen - lenToAlloc; if ( left < 24 || left < (lenToAlloc / 8) ) { // you get the whole thing. return loc; } // don't quantize: // - $ collections (indexes) as we already have those aligned the way we want SERVER-8425 if ( _normalCollection ) { // we quantize here so that it only impacts newly sized records // this prevents oddities with older records and space re-use SERVER-8435 lenToAlloc = std::min( r->lengthWithHeaders(), quantizeAllocationSpace( lenToAlloc ) ); left = regionlen - lenToAlloc; if ( left < 24 ) { // you get the whole thing. return loc; } } /* split off some for further use. */ txn->recoveryUnit()->writingInt(r->lengthWithHeaders()) = lenToAlloc; DiskLoc newDelLoc = loc; newDelLoc.inc(lenToAlloc); DeletedRecord* newDel = drec(newDelLoc); DeletedRecord* newDelW = txn->recoveryUnit()->writing(newDel); newDelW->extentOfs() = r->extentOfs(); newDelW->lengthWithHeaders() = left; newDelW->nextDeleted().Null(); addDeletedRec( txn, newDelLoc ); return loc; }