Status HostAndPort::initialize(const StringData& s) { const size_t colonPos = s.rfind(':'); const StringData hostPart = s.substr(0, colonPos); if (hostPart.empty()) { return Status(ErrorCodes::FailedToParse, str::stream() << "Empty host component parsing HostAndPort from \"" << escape(s.toString()) << "\""); } int port; if (colonPos != std::string::npos) { const StringData portPart = s.substr(colonPos + 1); Status status = parseNumberFromStringWithBase(portPart, 10, &port); if (!status.isOK()) { return status; } if (port <= 0) { return Status(ErrorCodes::FailedToParse, str::stream() << "Port number " << port << " out of range parsing HostAndPort from \"" << escape(s.toString()) << "\""); } } else { port = -1; } _host = hostPart.toString(); _port = port; return Status::OK(); }
std::ostream& MessageEventDetailsEncoder::encode(const MessageEventEphemeral& event, std::ostream &os) { static const size_t maxLogLine = 10 * 1024; char dateString[64]; curTimeString(dateString); os << dateString << ' '; StringData contextName = event.getContextName(); if (!contextName.empty()) { os << '[' << contextName << "] "; } LogSeverity severity = event.getSeverity(); if (severity >= LogSeverity::Info()) { os << severity << ": "; } StringData msg = event.getMessage(); if (msg.size() > maxLogLine) { os << "warning: log line attempted (" << msg.size() / 1024 << "k) over max size (" << maxLogLine / 1024 << "k), printing beginning and end ... "; os << msg.substr(0, maxLogLine / 3); os << " .......... "; os << msg.substr(msg.size() - (maxLogLine / 3)); } else { os << msg; } if (!msg.endsWith(StringData("\n", StringData::LiteralTag()))) os << '\n'; return os; }
std::ostream& MessageEventDetailsEncoder::encode(const MessageEventEphemeral& event, std::ostream& os) { static const size_t maxLogLine = 10 * 1024; _dateFormatter(os, event.getDate()); os << ' '; os << event.getSeverity().toChar(); os << ' '; LogComponent component = event.getComponent(); os << component; os << ' '; StringData contextName = event.getContextName(); if (!contextName.empty()) { os << '[' << contextName << "] "; } StringData msg = event.getMessage(); if (msg.size() > maxLogLine) { os << "warning: log line attempted (" << msg.size() / 1024 << "k) over max size (" << maxLogLine / 1024 << "k), printing beginning and end ... "; os << msg.substr(0, maxLogLine / 3); os << " .......... "; os << msg.substr(msg.size() - (maxLogLine / 3)); } else { os << msg; } if (!msg.endsWith(StringData("\n", StringData::LiteralTag()))) os << '\n'; return os; }
std::ostream& MessageEventDetailsEncoder::encode(const MessageEventEphemeral& event, std::ostream& os) { const auto maxLogSizeKB = getMaxLogSizeKB(); const size_t maxLogSize = maxLogSizeKB * 1024; getDateFormatter()(os, event.getDate()); os << ' '; const auto severity = event.getSeverity(); os << severity.toStringDataCompact(); os << ' '; LogComponent component = event.getComponent(); os << component; os << ' '; StringData contextName = event.getContextName(); if (!contextName.empty()) { os << '[' << contextName << "] "; } StringData msg = event.getMessage(); #ifdef _WIN32 // We need to translate embedded Unix style line endings into Windows style endings. std::string tempstr; size_t embeddedNewLine = msg.find('\n'); if (embeddedNewLine != std::string::npos) { tempstr = msg.toString().replace(embeddedNewLine, 1, "\r\n"); embeddedNewLine = tempstr.find('\n', embeddedNewLine + 2); while (embeddedNewLine != std::string::npos) { tempstr = tempstr.replace(embeddedNewLine, 1, "\r\n"); embeddedNewLine = tempstr.find('\n', embeddedNewLine + 2); } msg = tempstr; } #endif if (event.isTruncatable() && msg.size() > maxLogSize) { os << "warning: log line attempted (" << msg.size() / 1024 << "kB) over max size (" << maxLogSizeKB << "kB), printing beginning and end ... "; os << msg.substr(0, maxLogSize / 3); os << " .......... "; os << msg.substr(msg.size() - (maxLogSize / 3)); } else { os << msg; } if (!msg.endsWith(kEOL)) os << kEOL; return os; }
BSONElement extractElementAtPath(const BSONObj& obj, StringData path) { BSONElement e = obj.getField(path); if (e.eoo()) { size_t dot_offset = path.find('.'); if (dot_offset != std::string::npos) { StringData left = path.substr(0, dot_offset); StringData right = path.substr(dot_offset + 1); BSONObj sub = obj.getObjectField(left); return sub.isEmpty() ? BSONElement() : extractElementAtPath(sub, right); } } return e; }
bool FieldRef::equalsDottedField( const StringData& other ) const { StringData rest = other; for ( size_t i = 0; i < _size; i++ ) { StringData part = getPart( i ); if ( !rest.startsWith( part ) ) return false; if ( i == _size - 1 ) return rest.size() == part.size(); // make sure next thing is a dot if ( rest.size() == part.size() ) return false; if ( rest[part.size()] != '.' ) return false; rest = rest.substr( part.size() + 1 ); } return false; }
bool CmdAuthenticate::authenticateX509(const string& dbname, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result) { if(dbname != "$external") { errmsg = "X.509 authentication must always use the $external database."; result.append(saslCommandCodeFieldName, ErrorCodes::AuthenticationFailed); return false; } std::string user = cmdObj.getStringField("user"); ClientBasic *client = ClientBasic::getCurrent(); AuthorizationSession* authorizationSession = client->getAuthorizationSession(); StringData subjectName = client->port()->getX509SubjectName(); if (user != subjectName) { errmsg = "There is no x.509 client certificate matching the user."; result.append(saslCommandCodeFieldName, ErrorCodes::AuthenticationFailed); return false; } else { StringData srvSubjectName = getSSLManager()->getSubjectName(); StringData srvClusterId = srvSubjectName.substr(0, srvSubjectName.find("/CN")+1); StringData peerClusterId = subjectName.substr(0, subjectName.find("/CN")+1); // Handle internal cluster member if (srvClusterId == peerClusterId) { authorizationSession->grantInternalAuthorization(UserName(user, "$external")); } // Handle normal client authentication else { Principal* principal = new Principal(UserName(user, "$external")); principal->setImplicitPrivilegeAcquisition(true); authorizationSession->addAuthorizedPrincipal(principal); } result.append( "dbname" , dbname ); result.append( "user" , user ); return true; } }
Status CmdAuthenticate::_authenticateX509(const UserName& user, const BSONObj& cmdObj) { if(user.getDB() != "$external") { return Status(ErrorCodes::ProtocolError, "X.509 authentication must always use the $external database."); } ClientBasic *client = ClientBasic::getCurrent(); AuthorizationSession* authorizationSession = client->getAuthorizationSession(); StringData subjectName = client->port()->getX509SubjectName(); if (user.getUser() != subjectName) { return Status(ErrorCodes::AuthenticationFailed, "There is no x.509 client certificate matching the user."); } else { StringData srvSubjectName = getSSLManager()->getServerSubjectName(); StringData srvClusterId = srvSubjectName.substr(srvSubjectName.find(",OU=")); StringData peerClusterId = subjectName.substr(subjectName.find(",OU=")); fassert(17002, !srvClusterId.empty() && srvClusterId != srvSubjectName); // Handle internal cluster member auth, only applies to server-server connections if (srvClusterId == peerClusterId) { if (cmdLine.clusterAuthMode.empty() || cmdLine.clusterAuthMode == "keyfile") { return Status(ErrorCodes::AuthenticationFailed, "X509 authentication is not allowed for cluster authentication"); } authorizationSession->grantInternalAuthorization(user); } // Handle normal client authentication, only applies to client-server connections else { Principal* principal = new Principal(user); authorizationSession->addAndAuthorizePrincipal(principal); } return Status::OK(); } }
Status parseCertificateSelector(SSLParams::CertificateSelector* selector, StringData name, StringData value) { selector->subject.clear(); selector->thumbprint.clear(); const auto delim = value.find('='); if (delim == std::string::npos) { return {ErrorCodes::BadValue, str::stream() << "Certificate selector for '" << name << "' must be a key=value pair"}; } auto key = value.substr(0, delim); if (key == "subject") { selector->subject = value.substr(delim + 1).toString(); return Status::OK(); } if (key != "thumbprint") { return {ErrorCodes::BadValue, str::stream() << "Unknown certificate selector property for '" << name << "': '" << key << "'"}; } auto swHex = hexToVector(value.substr(delim + 1)); if (!swHex.isOK()) { return {ErrorCodes::BadValue, str::stream() << "Invalid certificate selector value for '" << name << "': " << swHex.getStatus().reason()}; } selector->thumbprint = std::move(swHex.getValue()); return Status::OK(); }
void StringData::inflate(int size, const StringData &fillval, bool before) { if ((int) buf.length() >= size) return; StringData dtVal; int iInflateSize = size - buf.length(); for (unsigned int i = 0; i < iInflateSize / fillval.length(); i++) { dtVal += fillval; } if (iInflateSize % fillval.length() > 0) dtVal += fillval.substr(0, iInflateSize % fillval.length()); if (before) operator =(dtVal + *this); else operator =(*this + dtVal); }
// Index works as follows: All non-NULL values are stored as if they had appended an 'X' character at the end. So // "foo" is stored as if it was "fooX", and "" (empty string) is stored as "X". And NULLs are stored as empty strings. inline StringIndex::key_type StringIndex::create_key(StringData str, size_t offset) noexcept { if (str.is_null()) return 0; if (offset > str.size()) return 0; // for very short strings size_t tail = str.size() - offset; if (tail <= sizeof(key_type)-1) { char buf[sizeof(key_type)]; memset(buf, 0, sizeof(key_type)); buf[tail] = 'X'; memcpy(buf, str.data() + offset, tail); return create_key(StringData(buf, tail + 1)); } // else fallback return create_key(str.substr(offset)); }
void WiredTigerKVEngine::_checkIdentPath(StringData ident) { size_t start = 0; size_t idx; while ((idx = ident.find('/', start)) != string::npos) { StringData dir = ident.substr(0, idx); boost::filesystem::path subdir = _path; subdir /= dir.toString(); if (!boost::filesystem::exists(subdir)) { LOG(1) << "creating subdirectory: " << dir; try { boost::filesystem::create_directory(subdir); } catch (const std::exception& e) { error() << "error creating path " << subdir.string() << ' ' << e.what(); throw; } } start = idx + 1; } }
StringData ObjectStore::object_type_for_table_name(StringData table_name) { if (table_name.begins_with(c_object_table_prefix)) { return table_name.substr(sizeof(c_object_table_prefix) - 1); } return StringData(); }
Status HostAndPort::initialize(const StringData& s) { size_t colonPos = s.rfind(':'); StringData hostPart = s.substr(0, colonPos); // handle ipv6 hostPart (which we require to be wrapped in []s) const size_t openBracketPos = s.find('['); const size_t closeBracketPos = s.find(']'); if (openBracketPos != std::string::npos) { if (openBracketPos != 0) { return Status(ErrorCodes::FailedToParse, str::stream() << "'[' present, but not first character in " << s.toString()); } if (closeBracketPos == std::string::npos) { return Status(ErrorCodes::FailedToParse, str::stream() << "ipv6 address is missing closing ']' in hostname in " << s.toString()); } hostPart = s.substr(openBracketPos+1, closeBracketPos-openBracketPos-1); // prevent accidental assignment of port to the value of the final portion of hostPart if (colonPos < closeBracketPos) { colonPos = std::string::npos; } else if (colonPos != closeBracketPos+1) { return Status(ErrorCodes::FailedToParse, str::stream() << "Extraneous characters between ']' and pre-port ':'" << " in " << s.toString()); } } else if (closeBracketPos != std::string::npos) { return Status(ErrorCodes::FailedToParse, str::stream() << "']' present without '[' in " << s.toString()); } if (hostPart.empty()) { return Status(ErrorCodes::FailedToParse, str::stream() << "Empty host component parsing HostAndPort from \"" << escape(s.toString()) << "\""); } int port; if (colonPos != std::string::npos) { const StringData portPart = s.substr(colonPos + 1); Status status = parseNumberFromStringWithBase(portPart, 10, &port); if (!status.isOK()) { return status; } if (port <= 0) { return Status(ErrorCodes::FailedToParse, str::stream() << "Port number " << port << " out of range parsing HostAndPort from \"" << escape(s.toString()) << "\""); } } else { port = -1; } _host = hostPart.toString(); _port = port; return Status::OK(); }
void CurOp::setNS( const StringData& ns ) { ns.substr( 0, Namespace::MaxNsLen ).copyTo( _ns, true ); }
void BtreeKeyGeneratorV1::getKeysImplWithArray( std::vector<const char*> fieldNames, std::vector<BSONElement> fixed, const BSONObj& obj, BSONObjSet* keys, unsigned numNotFound, const std::vector<PositionalPathInfo>& positionalInfo, MultikeyPaths* multikeyPaths) const { BSONElement arrElt; // A set containing the position of any indexed fields in the key pattern that traverse through // the 'arrElt' array value. std::set<size_t> arrIdxs; // A vector with size equal to the number of elements in the index key pattern. Each element in // the vector, if initialized, refers to the component within the indexed field that traverses // through the 'arrElt' array value. We say that this component within the indexed field // corresponds to a path that causes the index to be multikey if the 'arrElt' array value // contains multiple elements. // // For example, consider the index {'a.b': 1, 'a.c'} and the document // {a: [{b: 1, c: 'x'}, {b: 2, c: 'y'}]}. The path "a" causes the index to be multikey, so we'd // have a std::vector<boost::optional<size_t>>{{0U}, {0U}}. // // Furthermore, due to how positional key patterns are specified, it's possible for an indexed // field to cause the index to be multikey at a different component than another indexed field // that also traverses through the 'arrElt' array value. It's then also possible for an indexed // field not to cause the index to be multikey, even if it traverses through the 'arrElt' array // value, because only a particular element would be indexed. // // For example, consider the index {'a.b': 1, 'a.b.0'} and the document {a: {b: [1, 2]}}. The // path "a.b" causes the index to be multikey, but the key pattern "a.b.0" only indexes the // first element of the array, so we'd have a // std::vector<boost::optional<size_t>>{{1U}, boost::none}. std::vector<boost::optional<size_t>> arrComponents(fieldNames.size()); bool mayExpandArrayUnembedded = true; for (size_t i = 0; i < fieldNames.size(); ++i) { if (*fieldNames[i] == '\0') { continue; } bool arrayNestedArray; // Extract element matching fieldName[ i ] from object xor array. BSONElement e = extractNextElement(obj, positionalInfo[i], &fieldNames[i], &arrayNestedArray); if (e.eoo()) { // if field not present, set to null fixed[i] = nullElt; // done expanding this field name fieldNames[i] = ""; numNotFound++; } else if (e.type() == Array) { arrIdxs.insert(i); if (arrElt.eoo()) { // we only expand arrays on a single path -- track the path here arrElt = e; } else if (e.rawdata() != arrElt.rawdata()) { // enforce single array path here assertParallelArrays(e.fieldName(), arrElt.fieldName()); } if (arrayNestedArray) { mayExpandArrayUnembedded = false; } } else { // not an array - no need for further expansion fixed[i] = e; } } if (arrElt.eoo()) { // No array, so generate a single key. if (_isSparse && numNotFound == fieldNames.size()) { return; } BSONObjBuilder b(_sizeTracker); for (std::vector<BSONElement>::iterator i = fixed.begin(); i != fixed.end(); ++i) { CollationIndexKey::collationAwareIndexKeyAppend(*i, _collator, &b); } keys->insert(b.obj()); } else if (arrElt.embeddedObject().firstElement().eoo()) { // We've encountered an empty array. if (multikeyPaths && mayExpandArrayUnembedded) { // Any indexed path which traverses through the empty array must be recorded as an array // component. for (auto i : arrIdxs) { // We need to determine which component of the indexed field causes the index to be // multikey as a result of the empty array. Indexed empty arrays are considered // multikey and may occur mid-path. For instance, the indexed path "a.b.c" has // multikey components {0, 1} given the document {a: [{b: []}, {b: 1}]}. size_t fullPathLength = _pathLengths[i]; size_t suffixPathLength = FieldRef{fieldNames[i]}.numParts(); invariant(suffixPathLength < fullPathLength); arrComponents[i] = fullPathLength - suffixPathLength - 1; } } // For an empty array, set matching fields to undefined. _getKeysArrEltFixed(&fieldNames, &fixed, undefinedElt, keys, numNotFound, arrElt, arrIdxs, true, _emptyPositionalInfo, multikeyPaths); } else { BSONObj arrObj = arrElt.embeddedObject(); // For positional key patterns, e.g. {'a.1.b': 1}, we lookup the indexed array element // and then traverse the remainder of the field path up front. This prevents us from // having to look up the indexed element again on each recursive call (i.e. once per // array element). std::vector<PositionalPathInfo> subPositionalInfo(fixed.size()); for (size_t i = 0; i < fieldNames.size(); ++i) { const bool fieldIsArray = arrIdxs.find(i) != arrIdxs.end(); if (*fieldNames[i] == '\0') { // We've reached the end of the path. if (multikeyPaths && fieldIsArray && mayExpandArrayUnembedded) { // The 'arrElt' array value isn't expanded into multiple elements when the last // component of the indexed field is positional and 'arrElt' contains nested // array values. In all other cases, the 'arrElt' array value may be expanded // into multiple element and can therefore cause the index to be multikey. arrComponents[i] = _pathLengths[i] - 1; } continue; } // The earlier call to dps::extractElementAtPathOrArrayAlongPath(..., fieldNames[i]) // modified fieldNames[i] to refer to the suffix of the path immediately following the // 'arrElt' array value. If we haven't reached the end of this indexed field yet, then // we must have traversed through 'arrElt'. invariant(fieldIsArray); StringData part = fieldNames[i]; part = part.substr(0, part.find('.')); subPositionalInfo[i].positionallyIndexedElt = arrObj[part]; if (subPositionalInfo[i].positionallyIndexedElt.eoo()) { // We aren't indexing a particular element of the 'arrElt' array value, so it may be // expanded into multiple elements. It can therefore cause the index to be multikey. if (multikeyPaths) { // We need to determine which component of the indexed field causes the index to // be multikey as a result of the 'arrElt' array value. Since // // NumComponents("<pathPrefix>") + NumComponents("<pathSuffix>") // = NumComponents("<pathPrefix>.<pathSuffix>"), // // we can compute the number of components in a prefix of the indexed field by // subtracting the number of components in the suffix 'fieldNames[i]' from the // number of components in the indexed field '_fieldNames[i]'. // // For example, consider the indexed field "a.b.c" and the suffix "c". The path // "a.b.c" has 3 components and the suffix "c" has 1 component. Subtracting the // latter from the former yields the number of components in the prefix "a.b", // i.e. 2. size_t fullPathLength = _pathLengths[i]; size_t suffixPathLength = FieldRef{fieldNames[i]}.numParts(); invariant(suffixPathLength < fullPathLength); arrComponents[i] = fullPathLength - suffixPathLength - 1; } continue; } // We're indexing an array element by its position. Traverse the remainder of the // field path now. // // Indexing an array element by its position selects a particular element of the // 'arrElt' array value when generating keys. It therefore cannot cause the index to be // multikey. subPositionalInfo[i].arrayObj = arrObj; subPositionalInfo[i].remainingPath = fieldNames[i]; subPositionalInfo[i].dottedElt = dps::extractElementAtPathOrArrayAlongPath( arrObj, subPositionalInfo[i].remainingPath); } // Generate a key for each element of the indexed array. for (const auto arrObjElem : arrObj) { _getKeysArrEltFixed(&fieldNames, &fixed, arrObjElem, keys, numNotFound, arrElt, arrIdxs, mayExpandArrayUnembedded, subPositionalInfo, multikeyPaths); } } // Record multikey path components. if (multikeyPaths) { for (size_t i = 0; i < arrComponents.size(); ++i) { if (auto arrComponent = arrComponents[i]) { (*multikeyPaths)[i].insert(*arrComponent); } } } }
Status parseNumberFromStringWithBase( StringData stringValue, int base, NumberType* result) { typedef ::std::numeric_limits<NumberType> limits; if (base == 1 || base < 0 || base > 36) return Status(ErrorCodes::BadValue, "Invalid base", 0); bool isNegative = false; StringData str = _extractBase(_extractSign(stringValue, &isNegative), base, &base); if (str.empty()) return Status(ErrorCodes::FailedToParse, "No digits"); NumberType n(0); if (isNegative) { if (limits::is_signed) { for (size_t i = 0; i < str.size(); ++i) { NumberType digitValue = NumberType(_digitValue(str[i])); if (int(digitValue) >= base) { return Status(ErrorCodes::FailedToParse, "Bad digit \"" + str.substr(i, 1).toString() + "\" while parsing " + stringValue.toString()); } // MSVC: warning C4146: unary minus operator applied to unsigned type, result still unsigned // This code is statically known to be dead when NumberType is unsigned, so the warning is not real #pragma warning(push) #pragma warning(disable:4146) if ((NumberType(limits::min() / base) > n) || ((limits::min() - NumberType(n * base)) > -digitValue)) { #pragma warning(pop) return Status(ErrorCodes::FailedToParse, "Underflow"); } n *= NumberType(base); n -= NumberType(digitValue); } } else { return Status(ErrorCodes::FailedToParse, "Negative value"); } } else { for (size_t i = 0; i < str.size(); ++i) { NumberType digitValue = NumberType(_digitValue(str[i])); if (int(digitValue) >= base) { return Status(ErrorCodes::FailedToParse, "Bad digit \"" + str.substr(i, 1).toString() + "\" while parsing " + stringValue.toString()); } if ((NumberType(limits::max() / base) < n) || (NumberType(limits::max() - n * base) < digitValue)) { return Status(ErrorCodes::FailedToParse, "Overflow"); } n *= NumberType(base); n += NumberType(digitValue); } } *result = n; return Status::OK(); }
StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::_secondStep( OperationContext* opCtx, StringData inputData) { const auto badCount = [](int got) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect number of arguments for second SCRAM client message, got " << got << " expected at least 3"); }; /** * client-final-message-without-proof := cbind ',' nonce ',' [ ',' extensions ] * client-final-message := client-final-message-without-proof ',' proof */ const auto last_comma = inputData.rfind(','); if (last_comma == std::string::npos) { return badCount(1); } // add client-final-message-without-proof to authMessage const auto client_final_message_without_proof = inputData.substr(0, last_comma); _authMessage += "," + client_final_message_without_proof.toString(); const auto last_field = inputData.substr(last_comma + 1); if ((last_field.size() < 3) || !last_field.startsWith("p=")) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM ClientProof: " << last_field); } const auto proof = last_field.substr(2); const auto input = StringSplitter::split(client_final_message_without_proof.toString(), ","); if (input.size() < 2) { // Add count for proof back on. return badCount(input.size() + 1); } if (!str::startsWith(input[0], "c=") || input[0].size() < 3) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM channel binding: " << input[0]); } const auto cbind = input[0].substr(2); if (!str::startsWith(input[1], "r=") || input[1].size() < 6) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM client|server nonce: " << input[1]); } const auto nonce = input[1].substr(2); // Concatenated nonce sent by client should equal the one in server-first-message if (nonce != _nonce) { return Status(ErrorCodes::BadValue, str::stream() << "Unmatched SCRAM nonce received from client in second step, expected " << _nonce << " but received " << nonce); } // Do server side computations, compare storedKeys and generate client-final-message // AuthMessage := client-first-message-bare + "," + // server-first-message + "," + // client-final-message-without-proof // ClientSignature := HMAC(StoredKey, AuthMessage) // ClientKey := ClientSignature XOR ClientProof // ServerSignature := HMAC(ServerKey, AuthMessage) if (!_secrets.verifyClientProof(_authMessage, base64::decode(proof.toString()))) { return Status(ErrorCodes::AuthenticationFailed, "SCRAM authentication failed, storedKey mismatch"); } StringBuilder sb; // ServerSignature := HMAC(ServerKey, AuthMessage) sb << "v=" << _secrets.generateServerSignature(_authMessage); return std::make_tuple(false, sb.str()); }
StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::_firstStep( OperationContext* opCtx, StringData inputData) { const auto badCount = [](int got) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect number of arguments for first SCRAM client message, got " << got << " expected at least 3"); }; /** * gs2-cbind-flag := ("p=" cb-name) / 'y' / 'n' * gs2-header := gs2-cbind-flag ',' [ authzid ] ',' * reserved-mext := "m=" 1*(value-char) * client-first-message-bare := [reserved-mext ','] username ',' nonce [',' extensions] * client-first-message := gs2-header client-first-message-bare */ const auto gs2_cbind_comma = inputData.find(','); if (gs2_cbind_comma == std::string::npos) { return badCount(1); } const auto gs2_cbind_flag = inputData.substr(0, gs2_cbind_comma); if (gs2_cbind_flag.startsWith("p=")) { return Status(ErrorCodes::BadValue, "Server does not support channel binding"); } if ((gs2_cbind_flag != "y") && (gs2_cbind_flag != "n")) { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM client message prefix: " << gs2_cbind_flag); } const auto gs2_header_comma = inputData.find(',', gs2_cbind_comma + 1); if (gs2_header_comma == std::string::npos) { return badCount(2); } auto authzId = inputData.substr(gs2_cbind_comma + 1, gs2_header_comma - (gs2_cbind_comma + 1)); if (authzId.size()) { if (authzId.startsWith("a=")) { authzId = authzId.substr(2); } else { return Status(ErrorCodes::BadValue, str::stream() << "Incorrect SCRAM authzid: " << authzId); } } const auto client_first_message_bare = inputData.substr(gs2_header_comma + 1); if (client_first_message_bare.startsWith("m=")) { return Status(ErrorCodes::BadValue, "SCRAM mandatory extensions are not supported"); } /* StringSplitter::split() will ignore consecutive delimiters. * e.g. "foo,,bar" => {"foo","bar"} * This makes our implementation of SCRAM *slightly* more generous * in what it will accept than the standard calls for. * * This does not impact _authMessage, as it's composed from the raw * string input, rather than the output of the split operation. */ const auto input = StringSplitter::split(client_first_message_bare.toString(), ","); if (input.size() < 2) { // gs2-header is not included in this count, so add it back in. return badCount(input.size() + 2); } if (!str::startsWith(input[0], "n=") || input[0].size() < 3) { return Status(ErrorCodes::BadValue, str::stream() << "Invalid SCRAM user name: " << input[0]); } ServerMechanismBase::_principalName = input[0].substr(2); decodeSCRAMUsername(ServerMechanismBase::_principalName); if (!authzId.empty() && ServerMechanismBase::_principalName != authzId) { return Status(ErrorCodes::BadValue, str::stream() << "SCRAM user name " << ServerMechanismBase::_principalName << " does not match authzid " << authzId); } if (!str::startsWith(input[1], "r=") || input[1].size() < 6) { return Status(ErrorCodes::BadValue, str::stream() << "Invalid SCRAM client nonce: " << input[1]); } const auto clientNonce = input[1].substr(2); // 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(ServerMechanismBase::ServerMechanismBase::_principalName, ServerMechanismBase::getAuthenticationDatabase()); if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1") && user != internalSecurity.user->getName()) { return Status(ErrorCodes::BadValue, "SCRAM-SHA-1 authentication is disabled"); } // The authentication database is also the source database for the user. User* userObj; auto authManager = AuthorizationManager::get(opCtx->getServiceContext()); Status status = authManager->acquireUser(opCtx, user, &userObj); if (!status.isOK()) { return status; } User::CredentialData credentials = userObj->getCredentials(); UserName userName = userObj->getName(); authManager->releaseUser(userObj); _scramCredentials = credentials.scram<HashBlock>(); if (!_scramCredentials.isValid()) { // Check for authentication attempts of the __system user on // systems started without a keyfile. if (userName == internalSecurity.user->getName()) { return Status(ErrorCodes::AuthenticationFailed, "It is not possible to authenticate as the __system user " "on servers started without a --keyFile parameter"); } else { return Status(ErrorCodes::AuthenticationFailed, "Unable to perform SCRAM authentication for a user with missing " "or invalid SCRAM credentials"); } } _secrets = scram::Secrets<HashBlock>("", base64::decode(_scramCredentials.storedKey), base64::decode(_scramCredentials.serverKey)); // 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]; std::unique_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=" << _scramCredentials.salt << ",i=" << _scramCredentials.iterationCount; std::string outputData = sb.str(); // add client-first-message-bare and server-first-message to _authMessage _authMessage = client_first_message_bare.toString() + "," + outputData; return std::make_tuple(false, std::move(outputData)); }
bool processObj(const BSONObj &obj) { if (obj.hasField("$err")) { log() << "error getting oplog: " << obj << endl; return false; } static const char *names[] = {"ts", "op", "ns", "o", "b"}; BSONElement fields[5]; obj.getFields(5, names, fields); BSONElement &tsElt = fields[0]; if (!tsElt.ok()) { log() << "oplog format error: " << obj << " missing 'ts' field." << endl; return false; } if (tsElt.type() != Date && tsElt.type() != Timestamp) { log() << "oplog format error: " << obj << " wrong 'ts' field type." << endl; return false; } _thisTime = OpTime(tsElt.date()); BSONElement &opElt = fields[1]; if (!opElt.ok()) { log() << "oplog format error: " << obj << " missing 'op' field." << endl; return false; } StringData op = opElt.Stringdata(); // nop if (op == "n") { return true; } // "presence of a database" if (op == "db") { return true; } if (op != "c" && op != "i" && op != "u" && op != "d") { log() << "oplog format error: " << obj << " has an invalid 'op' field of '" << op << "'." << endl; return false; } if (op != "i" && !_insertBuf.empty()) { flushInserts(); } BSONElement &nsElt = fields[2]; if (!nsElt.ok()) { log() << "oplog format error: " << obj << " missing 'ns' field." << endl; return false; } StringData ns = nsElt.Stringdata(); size_t i = ns.find('.'); if (i == string::npos) { log() << "oplog format error: invalid namespace '" << ns << "' in op " << obj << "." << endl; return false; } StringData dbname = ns.substr(0, i); StringData collname = ns.substr(i + 1); BSONElement &oElt = fields[3]; if (!oElt.ok()) { log() << "oplog format error: " << obj << " missing 'o' field." << endl; return false; } BSONObj o = obj["o"].Obj(); if (op == "c") { if (collname != "$cmd") { log() << "oplog format error: invalid namespace '" << ns << "' for command in op " << obj << "." << endl; return false; } BSONObj info; bool ok = _conn.runCommand(dbname.toString(), o, info); if (!ok) { StringData fieldName = o.firstElementFieldName(); BSONElement errmsgElt = info["errmsg"]; StringData errmsg = errmsgElt.type() == String ? errmsgElt.Stringdata() : ""; bool isDropIndexes = (fieldName == "dropIndexes" || fieldName == "deleteIndexes"); if (((fieldName == "drop" || isDropIndexes) && errmsg == "ns not found") || (isDropIndexes && (errmsg == "index not found" || errmsg.find("can't find index with key:") == 0))) { // This is actually ok. We don't mind dropping something that's not there. LOG(1) << "Tried to replay " << o << ", got " << info << ", ignoring." << endl; } else { log() << "replay of command " << o << " failed: " << info << endl; return false; } } } else { string nsstr = ns.toString(); if (op == "i") { if (collname == "system.indexes") { // Can't ensure multiple indexes in the same batch. flushInserts(); // For now, we need to strip out any background fields from // ensureIndex. Once we do hot indexing we can do something more // like what vanilla applyOperation_inlock does. if (o["background"].trueValue()) { BSONObjBuilder builder; BSONObjIterator it(o); while (it.more()) { BSONElement e = it.next(); if (strncmp(e.fieldName(), "background", sizeof("background")) != 0) { builder.append(e); } } o = builder.obj(); } // We need to warn very carefully about dropDups. if (o["dropDups"].trueValue()) { BSONObjBuilder builder; BSONObjIterator it(o); while (it.more()) { BSONElement e = it.next(); if (strncmp(e.fieldName(), "dropDups", sizeof("dropDups")) != 0) { builder.append(e); } } warning() << "Detected an ensureIndex with dropDups: true in " << o << "." << endl; warning() << "This option is not supported in TokuMX, because it deletes arbitrary data." << endl; warning() << "If it were replayed, it could result in a completely different data set than the source database." << endl; warning() << "We will attempt to replay it without dropDups, but if that fails, you must restart your migration process." << endl; _conn.insert(nsstr, o); string err = _conn.getLastError(dbname.toString(), false, false); if (!err.empty()) { log() << "replay of operation " << obj << " failed: " << err << endl; warning() << "You cannot continue processing this replication stream. You need to restart the migration process." << endl; _running = false; _logAtExit = false; return true; } } } pushInsert(nsstr, o); // Don't call GLE or update _maxOpTimeSynced yet. _thisTime = OpTime(); return true; } else if (op == "u") { BSONElement o2Elt = obj["o2"]; if (!o2Elt.ok()) { log() << "oplog format error: " << obj << " missing 'o2' field." << endl; return false; } BSONElement &bElt = fields[4]; bool upsert = bElt.booleanSafe(); BSONObj o2 = o2Elt.Obj(); _conn.update(nsstr, o2, o, upsert, false); } else if (op == "d") { BSONElement &bElt = fields[4]; bool justOne = bElt.booleanSafe(); _conn.remove(nsstr, o, justOne); } string err = _conn.getLastError(dbname.toString(), false, false); if (!err.empty()) { log() << "replay of operation " << obj << " failed: " << err << endl; return false; } } // If we got here, we completed the operation successfully. _maxOpTimeSynced = _thisTime; _thisTime = OpTime(); return true; }
inline StringData table_name_to_class_name(StringData table_name) { REALM_ASSERT(table_name.begins_with("class_")); return table_name.substr(6); }