diff options
Diffstat (limited to 'indra/newview/llscripteditorws.cpp')
| -rw-r--r-- | indra/newview/llscripteditorws.cpp | 147 |
1 files changed, 92 insertions, 55 deletions
diff --git a/indra/newview/llscripteditorws.cpp b/indra/newview/llscripteditorws.cpp index 178b03d4da..ddc3e25d7b 100644 --- a/indra/newview/llscripteditorws.cpp +++ b/indra/newview/llscripteditorws.cpp @@ -73,6 +73,8 @@ #include <array> +#include <openssl/rand.h> + namespace { // Per-operation timeouts (seconds) for coroutine-based async RPC handlers. @@ -81,6 +83,11 @@ namespace constexpr F32 NOTECARD_UPLOAD_TIMEOUT = 30.0f; constexpr F32 ITEM_CREATE_TIMEOUT = 30.0f; + // Seconds a client has to answer the handshake: plenty to read a + // file, and no longer than that is a connection kept that has not + // proven itself. + constexpr F64 HANDSHAKE_TIMEOUT = 30.0; + // Linkset flush coalescing delays (seconds). constexpr F32 LINKSET_ADD_FLUSH_DELAY = 5.0f; constexpr F32 LINKSET_REMOVE_FLUSH_DELAY = 0.2f; @@ -1256,7 +1263,7 @@ void LLScriptEditorWSServer::broadcastLanguageChange() if (isRunning()) { - broadcastNotification("language.syntax.change", params); + notifyAll("language.syntax.change", params); } } } @@ -2365,7 +2372,7 @@ void LLScriptEditorWSServer::notifyConnection(U32 connection_id, const std::stri if (it != mActiveConnections.end()) { auto connection = it->second.lock(); - if (connection) + if (connection && connection->isAuthenticated()) { connection->notify(method, params); } @@ -2385,7 +2392,7 @@ void LLScriptEditorWSServer::notifyAll(const std::string& method, const LLSD& pa for (const auto& pair : mActiveConnections) { auto connection = pair.second.lock(); - if (connection) + if (connection && connection->isAuthenticated()) { connection->sendMessage(payload); } @@ -2816,11 +2823,14 @@ void LLScriptEditorWSConnection::sendHandshake() handshake["agent_id"] = gAgent.getID(); handshake["agent_name"] = gAgentUsername; - std::string challenge_file = generateChallenge(); - if (!challenge_file.empty()) + const Challenge challenge = writeChallenge(); + if (challenge.mFile.empty()) { - handshake["challenge"] = challenge_file; + // Nothing to prove itself by, so nothing it may do. + sendDisconnect(DisconnectReason::INTERNAL_ERROR, "Unable to issue a challenge"); + return; } + handshake["challenge"] = challenge.mFile; LLSD languages = LLSD::emptyArray(); languages.append("lsl"); @@ -2839,22 +2849,33 @@ void LLScriptEditorWSConnection::sendHandshake() wptr_t that = weak_from_this(); - // Send session.handshake method call and the response - call("session.handshake", handshake, [that](const LLSD& result, const LLSD& error) { - if (error.isUndefined()) + // The answer comes once, whichever way: from the client, as the + // time runs out, or as the connection closes. + const LLSD sent = call( + "session.handshake", handshake, + [that, challenge](const LLSD& result, const LLSD& error) { + // Whatever the answer, the file has done its work. + LLFile::remove(challenge.mFile); auto self = that.lock(); - if (self) + if (!self) { - self->handleHandshakeResponse(result); + return; } - } - else - { - LL_WARNS("ScriptEditorWS") << "Handshake failed: " - << error["message"].asString() << LL_ENDL; - } - }); + if (error.isDefined()) + { + self->handleHandshakeError(error); + return; + } + self->handleHandshakeResponse(result, challenge.mSecret); + }, + HANDSHAKE_TIMEOUT); + if (sent.isUndefined()) + { + LLFile::remove(challenge.mFile); + handleHandshakeError(LLSD().with("code", LLJSONRPCConnection::RPCError::INTERNAL_ERROR).with("message", "Handshake not sent")); + return; + } LL_INFOS("ScriptEditorWS") << "Sent handshake call to new editor client" << LL_ENDL; } @@ -2886,40 +2907,36 @@ void LLScriptEditorWSConnection::sendDisconnect(DisconnectReason reason, const s closeConnection(1000, message); } -void LLScriptEditorWSConnection::handleHandshakeResponse(const LLSD& result) +void LLScriptEditorWSConnection::handleHandshakeError(const LLSD& error) { - LL_PROFILE_ZONE_SCOPED_CATEGORY_SCRIPTDEV; - LL_INFOS("ScriptEditorWS") << "Processing handshake response from client" << LL_ENDL; - - mClientName = result["client_name"].asString(); - mClientVersion = result["client_version"].asString(); - mProtocolVersion = result["protocol_version"].asString(); - - // Validate challenge response (if a challenge was issued). - const bool challenge_issued = mChallenge.notNull(); - bool valid_response = true; - if (challenge_issued) + const S32 code = error["code"].asInteger(); + if (code == LLJSONRPCConnection::RPCError::CONNECTION_CLOSED) { - valid_response = result.has("challenge_response") && - (result["challenge_response"].asUUID() == mChallenge); - mChallenge.setNull(); + // Gone already. + return; } + LL_WARNS("ScriptEditorWS") << "Handshake failed: " << error["message"].asString() << LL_ENDL; + sendDisconnect(code == LLJSONRPCConnection::RPCError::REQUEST_TIMEOUT ? DisconnectReason::TIMEOUT : DisconnectReason::PROTOCOL_ERROR, + "Handshake failed"); +} - // Always clean up the temporary challenge file if one was created, - // regardless of validation outcome. - if (!mChallengeFile.empty()) - { - LLFile::remove(mChallengeFile); - mChallengeFile.clear(); - } +void LLScriptEditorWSConnection::handleHandshakeResponse(const LLSD& result, const LLUUID& secret) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SCRIPTDEV; + LL_INFOS("ScriptEditorWS") << "Processing handshake response from client" << LL_ENDL; - if (challenge_issued && !valid_response) + // Only something that could read the file knows what it says. + if (!result.isMap() || result["challenge_response"].asUUID() != secret) { LL_WARNS("ScriptEditorWS") << "Invalid or missing challenge response from client" << LL_ENDL; sendDisconnect(DisconnectReason::PROTOCOL_ERROR, "Invalid challenge response"); return; } + mClientName = result["client_name"].asString(); + mClientVersion = result["client_version"].asString(); + mProtocolVersion = result["protocol_version"].asString(); + if (mProtocolVersion != "1.0") { LL_WARNS("ScriptEditorWS") << "Protocol version mismatch. Expected: 1.0, Got: " @@ -2945,29 +2962,49 @@ void LLScriptEditorWSConnection::handleHandshakeResponse(const LLSD& result) } } + // Let in before it is told so: what it asks on hearing it must be + // answered. + setAuthenticated(true); notify("session.ok"); LL_INFOS("ScriptEditorWS") << "Handshake completed successfully." << LL_ENDL; } -std::string LLScriptEditorWSConnection::generateChallenge() +// static +LLScriptEditorWSConnection::Challenge LLScriptEditorWSConnection::writeChallenge() { - mChallenge.generate(); - - mChallengeFile = std::string(LLFile::tmpdir()) + "sl_script_challenge_" + mChallenge.asString() + ".tmp"; - - llofstream file(mChallengeFile.c_str()); - if (!file.is_open()) + // A secret nobody could work out: a new id is made of the time and + // the network card's address, which are anybody's to know. + Challenge challenge; + if (RAND_bytes(challenge.mSecret.mData, UUID_BYTES) != 1) { - LL_WARNS("ScriptEditorWS") << "Unable to open challenge file: " << mChallengeFile << LL_ENDL; - mChallenge.setNull(); - mChallengeFile.clear(); - return std::string(); + LL_WARNS("ScriptEditorWS") << "Unable to make a challenge secret" << LL_ENDL; + return {}; } - file << mChallenge; - file.close(); + // Named for nothing it holds, made new rather than written over + // anything, or through a link, already by that name, and readable by + // the user alone: the name goes to whoever connects, and the temp + // folder can be everyone's. + const std::string file = LLFile::tmpdir() + "sl_script_challenge_" + LLUUID::generateNewID().asString() + ".tmp"; + const std::string text = challenge.mSecret.asString(); + std::error_code ec; + LLFile out(file, LLFile::out | LLFile::noreplace, ec, 0600); + if (ec) + { + // Whatever is there by that name is somebody else's, and stays. + LL_WARNS("ScriptEditorWS") << "Unable to make challenge file " << file << ": " << ec.message() << LL_ENDL; + return {}; + } + const bool written = out.write(text.data(), static_cast<S64>(text.size()), ec) == static_cast<S64>(text.size()) && !ec; + if (out.close(ec) != 0 || !written) + { + LL_WARNS("ScriptEditorWS") << "Unable to write challenge file " << file << LL_ENDL; + LLFile::remove(file); + return {}; + } - return mChallengeFile; + challenge.mFile = file; + return challenge; } |
