/*
     * 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() );
    }
Example #3
0
/**
 * 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;
    }