summaryrefslogtreecommitdiff
path: root/indra/newview/lllogininstance.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/lllogininstance.cpp')
-rw-r--r--indra/newview/lllogininstance.cpp1260
1 files changed, 630 insertions, 630 deletions
diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp
index c24a13f3b2..282a273be6 100644
--- a/indra/newview/lllogininstance.cpp
+++ b/indra/newview/lllogininstance.cpp
@@ -1,630 +1,630 @@
-/**
- * @file lllogininstance.cpp
- * @brief Viewer's host for a login connection.
- *
- * $LicenseInfo:firstyear=2009&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "llviewerprecompiledheaders.h"
-
-#include "lllogininstance.h"
-
-// llcommon
-#include "llevents.h"
-#include "stringize.h"
-#include "llsdserialize.h"
-
-// llmessage (!)
-#include "llfiltersd2xmlrpc.h" // for xml_escape_string()
-
-// login
-#include "lllogin.h"
-
-// newview
-#include "llhasheduniqueid.h"
-#include "llviewernetwork.h"
-#include "llviewercontrol.h"
-#include "llversioninfo.h"
-#include "llslurl.h"
-#include "llstartup.h"
-#include "llfloaterreg.h"
-#include "llnotifications.h"
-#include "llnotificationsutil.h"
-#include "llwindow.h"
-#include "llviewerwindow.h"
-#include "llprogressview.h"
-#include "llsecapi.h"
-#include "llstartup.h"
-#include "llmachineid.h"
-#include "llevents.h"
-#include "llappviewer.h"
-#include "llsdserialize.h"
-#include "lltrans.h"
-
-#include <boost/scoped_ptr.hpp>
-#include <boost/regex.hpp>
-#include <sstream>
-
-const S32 LOGIN_MAX_RETRIES = 0; // Viewer should not autmatically retry login
-const F32 LOGIN_SRV_TIMEOUT_MIN = 10;
-const F32 LOGIN_SRV_TIMEOUT_MAX = 120;
-const F32 LOGIN_DNS_TIMEOUT_FACTOR = 0.9; // make DNS wait shorter then retry time
-
-class LLLoginInstance::Disposable {
-public:
- virtual ~Disposable() {}
-};
-
-static const char * const TOS_REPLY_PUMP = "lllogininstance_tos_callback";
-static const char * const TOS_LISTENER_NAME = "lllogininstance_tos";
-
-std::string construct_start_string();
-
-// LLLoginInstance
-//-----------------------------------------------------------------------------
-
-
-LLLoginInstance::LLLoginInstance() :
- mLoginModule(new LLLogin()),
- mNotifications(NULL),
- mLoginState("offline"),
- mSaveMFA(true),
- mAttemptComplete(false),
- mTransferRate(0.0f),
- mDispatcher("LLLoginInstance", "change")
-{
- mLoginModule->getEventPump().listen("lllogininstance",
- boost::bind(&LLLoginInstance::handleLoginEvent, this, _1));
- // This internal use of LLEventDispatcher doesn't really need
- // per-function descriptions.
- mDispatcher.add("fail.login", "", boost::bind(&LLLoginInstance::handleLoginFailure, this, _1));
- mDispatcher.add("connect", "", boost::bind(&LLLoginInstance::handleLoginSuccess, this, _1));
- mDispatcher.add("disconnect", "", boost::bind(&LLLoginInstance::handleDisconnect, this, _1));
- mDispatcher.add("indeterminate", "", boost::bind(&LLLoginInstance::handleIndeterminate, this, _1));
-}
-
-void LLLoginInstance::setPlatformInfo(const std::string platform,
- const std::string platform_version,
- const std::string platform_name)
-{
- mPlatform = platform;
- mPlatformVersion = platform_version;
- mPlatformVersionName = platform_name;
-}
-
-LLLoginInstance::~LLLoginInstance()
-{
-}
-
-void LLLoginInstance::connect(LLPointer<LLCredential> credentials)
-{
- std::vector<std::string> uris;
- LLGridManager::getInstance()->getLoginURIs(uris);
- if (uris.size() < 1)
- {
- LL_WARNS() << "Failed to get login URIs during connect. No connect for you!" << LL_ENDL;
- return;
- }
- connect(uris.front(), credentials);
-}
-
-void LLLoginInstance::connect(const std::string& uri, LLPointer<LLCredential> credentials)
-{
- mAttemptComplete = false; // Reset attempt complete at this point!
- constructAuthParams(credentials);
- mLoginModule->connect(uri, mRequestData);
-}
-
-void LLLoginInstance::reconnect()
-{
- // Sort of like connect, only using the pre-existing
- // request params.
- std::vector<std::string> uris;
- LLGridManager::getInstance()->getLoginURIs(uris);
- mLoginModule->connect(uris.front(), mRequestData);
- gViewerWindow->setShowProgress(true);
-}
-
-void LLLoginInstance::disconnect()
-{
- mAttemptComplete = false; // Reset attempt complete at this point!
- mRequestData.clear();
- mLoginModule->disconnect();
-}
-
-LLSD LLLoginInstance::getResponse()
-{
- return mResponseData;
-}
-
-void LLLoginInstance::constructAuthParams(LLPointer<LLCredential> user_credential)
-{
- // Set up auth request options.
-//#define LL_MINIMIAL_REQUESTED_OPTIONS
- LLSD requested_options;
- // *Note: this is where gUserAuth used to be created.
- requested_options.append("inventory-root");
- requested_options.append("inventory-skeleton");
- //requested_options.append("inventory-meat");
- //requested_options.append("inventory-skel-targets");
-#if (!defined LL_MINIMIAL_REQUESTED_OPTIONS)
-
- // Not requesting library will trigger mFatalNoLibraryRootFolder
- requested_options.append("inventory-lib-root");
- requested_options.append("inventory-lib-owner");
- requested_options.append("inventory-skel-lib");
- // requested_options.append("inventory-meat-lib");
-
- requested_options.append("initial-outfit");
- requested_options.append("gestures");
- requested_options.append("display_names");
- requested_options.append("event_categories");
- requested_options.append("event_notifications");
- requested_options.append("classified_categories");
- requested_options.append("adult_compliant");
- requested_options.append("buddy-list");
- requested_options.append("newuser-config");
- requested_options.append("ui-config");
-
- //send this info to login.cgi for stats gathering
- //since viewerstats isn't reliable enough
- requested_options.append("advanced-mode");
-
-#endif
- requested_options.append("max-agent-groups");
- requested_options.append("map-server-url");
- requested_options.append("voice-config");
- requested_options.append("tutorial_setting");
- requested_options.append("login-flags");
- requested_options.append("global-textures");
- if(gSavedSettings.getBOOL("ConnectAsGod"))
- {
- gSavedSettings.setBOOL("UseDebugMenus", true);
- requested_options.append("god-connect");
- }
-
- LLSD request_params;
-
- unsigned char hashed_unique_id_string[MD5HEX_STR_SIZE];
- if ( ! llHashedUniqueID(hashed_unique_id_string) )
- {
-
- LL_WARNS("LLLogin") << "Not providing a unique id in request params" << LL_ENDL;
-
- }
- request_params["start"] = construct_start_string();
- request_params["agree_to_tos"] = false; // Always false here. Set true in
- request_params["read_critical"] = false; // handleTOSResponse
- request_params["last_exec_event"] = mLastExecEvent;
- request_params["last_exec_duration"] = mLastExecDuration;
- request_params["mac"] = (char*)hashed_unique_id_string;
- request_params["version"] = LLVersionInfo::instance().getVersion();
- request_params["channel"] = LLVersionInfo::instance().getChannel();
- request_params["platform"] = mPlatform;
- request_params["address_size"] = ADDRESS_SIZE;
- request_params["platform_version"] = mPlatformVersion;
- request_params["platform_string"] = mPlatformVersionName;
- request_params["id0"] = mSerialNumber;
- request_params["host_id"] = gSavedSettings.getString("HostID");
- request_params["extended_errors"] = true; // request message_id and message_args
- request_params["token"] = "";
-
- // log request_params _before_ adding the credentials or sensitive MFA hash data
- LL_DEBUGS("LLLogin") << "Login parameters: " << LLSDOStreamer<LLSDNotationFormatter>(request_params) << LL_ENDL;
-
- // Copy the credentials into the request after logging the rest
- LLSD credentials(user_credential->getLoginParams());
- for (LLSD::map_const_iterator it = credentials.beginMap();
- it != credentials.endMap();
- it++
- )
- {
- request_params[it->first] = it->second;
- }
-
- std::string mfa_hash = gSavedSettings.getString("MFAHash"); //non-persistent to enable testing
- std::string grid(LLGridManager::getInstance()->getGridId());
- std::string user_id = user_credential->userID();
- if (gSecAPIHandler)
- {
- if (mfa_hash.empty())
- {
- // normal execution, mfa_hash was not set from debug setting so load from protected store
- LLSD data_map = gSecAPIHandler->getProtectedData("mfa_hash", grid);
- if (data_map.isMap() && data_map.has(user_id))
- {
- mfa_hash = data_map[user_id].asString();
- }
- }
- else
- {
- // SL-16888 the mfa_hash is being overridden for testing so save it for consistency for future login requests
- gSecAPIHandler->addToProtectedMap("mfa_hash", grid, user_id, mfa_hash);
- }
- }
- else
- {
- LL_WARNS() << "unable to access protected store for mfa_hash" << LL_ENDL;
- }
-
- request_params["mfa_hash"] = mfa_hash;
-
- // Specify desired timeout/retry options
- LLSD http_params;
- F32 srv_timeout = llclamp(gSavedSettings.getF32("LoginSRVTimeout"), LOGIN_SRV_TIMEOUT_MIN, LOGIN_SRV_TIMEOUT_MAX);
- http_params["timeout"] = srv_timeout;
- http_params["retries"] = LOGIN_MAX_RETRIES;
- http_params["DNSCacheTimeout"] = srv_timeout * LOGIN_DNS_TIMEOUT_FACTOR; //Default: indefinite
-
- mRequestData.clear();
- mRequestData["method"] = "login_to_simulator";
- mRequestData["params"] = request_params;
- mRequestData["options"] = requested_options;
- mRequestData["http_params"] = http_params;
-#if LL_RELEASE_FOR_DOWNLOAD
- mRequestData["wait_for_updater"] = LLAppViewer::instance()->waitForUpdater();
-#else
- mRequestData["wait_for_updater"] = false;
-#endif
-}
-
-bool LLLoginInstance::handleLoginEvent(const LLSD& event)
-{
- LL_DEBUGS("LLLogin") << "LoginListener called!: \n" << event << LL_ENDL;
-
- if(!(event.has("state") && event.has("change") && event.has("progress")))
- {
- LL_ERRS("LLLogin") << "Unknown message from LLLogin: " << event << LL_ENDL;
- }
-
- mLoginState = event["state"].asString();
- mResponseData = event["data"];
-
- if(event.has("transfer_rate"))
- {
- mTransferRate = event["transfer_rate"].asReal();
- }
-
- // Call the method registered in constructor, if any, for more specific
- // handling
- mDispatcher.try_call(event);
- return false;
-}
-
-void LLLoginInstance::handleLoginFailure(const LLSD& event)
-{
- // TODO: we are handling failure in two separate places -
- // here and in STATE_LOGIN_PROCESS_RESPONSE processing
- // consider uniting them.
-
- // Login has failed.
- // Figure out why and respond...
- LLSD response = event["data"];
- LLSD updater = response["updater"];
-
- // Always provide a response to the updater, if in fact the updater
- // contacted us, if in fact the ping contains a 'reply' key. Most code
- // paths tell it not to proceed with updating.
- ResponsePtr resp(std::make_shared<LLEventAPI::Response>
- (LLSDMap("update", false), updater));
-
- std::string reason_response = response["reason"].asString();
- std::string message_response = response["message"].asString();
- LL_DEBUGS("LLLogin") << "reason " << reason_response
- << " message " << message_response
- << LL_ENDL;
- // For the cases of critical message or TOS agreement,
- // start the TOS dialog. The dialog response will be handled
- // by the LLLoginInstance::handleTOSResponse() callback.
- // The callback intiates the login attempt next step, either
- // to reconnect or to end the attempt in failure.
- if(reason_response == "tos")
- {
- LL_INFOS("LLLogin") << " ToS" << LL_ENDL;
-
- LLSD data(LLSD::emptyMap());
- data["message"] = message_response;
- data["reply_pump"] = TOS_REPLY_PUMP;
- if (gViewerWindow)
- gViewerWindow->setShowProgress(false);
- LLFloaterReg::showInstance("message_tos", data);
- LLEventPumps::instance().obtain(TOS_REPLY_PUMP)
- .listen(TOS_LISTENER_NAME,
- boost::bind(&LLLoginInstance::handleTOSResponse,
- this, _1, "agree_to_tos"));
- }
- else if(reason_response == "critical")
- {
- LL_INFOS("LLLogin") << "LLLoginInstance::handleLoginFailure Crit" << LL_ENDL;
-
- LLSD data(LLSD::emptyMap());
- data["message"] = message_response;
- data["reply_pump"] = TOS_REPLY_PUMP;
- if(response.has("error_code"))
- {
- data["error_code"] = response["error_code"];
- }
- if(response.has("certificate"))
- {
- data["certificate"] = response["certificate"];
- }
-
- if (gViewerWindow)
- gViewerWindow->setShowProgress(false);
-
- LLFloaterReg::showInstance("message_critical", data);
- LLEventPumps::instance().obtain(TOS_REPLY_PUMP)
- .listen(TOS_LISTENER_NAME,
- boost::bind(&LLLoginInstance::handleTOSResponse,
- this, _1, "read_critical"));
- }
- else if(reason_response == "update")
- {
- // This can happen if the user clicked Login quickly, before we heard
- // back from the Viewer Version Manager, but login failed because
- // login.cgi is insisting on a required update. We were called with an
- // event that bundles both the login.cgi 'response' and the
- // synchronization event from the 'updater'.
- std::string login_version = response["message_args"]["VERSION"];
- std::string vvm_version = updater["VERSION"];
- std::string relnotes = updater["URL"];
- LL_WARNS("LLLogin") << "Login failed because an update to version " << login_version << " is required." << LL_ENDL;
- // vvm_version might be empty because we might not have gotten
- // SLVersionChecker's LoginSync handshake. But if it IS populated, it
- // should (!) be the same as the version we got from login.cgi.
- if ((! vvm_version.empty()) && vvm_version != login_version)
- {
- LL_WARNS("LLLogin") << "VVM update version " << vvm_version
- << " differs from login version " << login_version
- << "; presenting VVM version to match release notes URL"
- << LL_ENDL;
- login_version = vvm_version;
- }
- if (relnotes.empty() || relnotes.find("://") == std::string::npos)
- {
- relnotes = LLTrans::getString("RELEASE_NOTES_BASE_URL");
- if (!LLStringUtil::endsWith(relnotes, "/"))
- relnotes += "/";
- relnotes += LLURI::escape(login_version) + ".html";
- }
-
- if (gViewerWindow)
- gViewerWindow->setShowProgress(false);
-
- LLSD args;
- args["VERSION"] = login_version;
- args["URL"] = relnotes;
-
- if (updater.isUndefined())
- {
- // If the updater failed to shake hands, better advise the user to
- // download the update him/herself.
- LLNotificationsUtil::add(
- "RequiredUpdate",
- args,
- updater,
- boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2));
- }
- else
- {
- // If we've heard from the updater that an update is required,
- // then display the prompt that assures the user we'll take care
- // of it. This is the one case in which we bind 'resp':
- // instead of destroying our Response object (and thus sending a
- // negative reply to the updater) as soon as we exit this
- // function, bind our shared_ptr so it gets passed into
- // syncWithUpdater. That ensures that the response is delayed
- // until the user has responded to the notification.
- LLNotificationsUtil::add(
- "PauseForUpdate",
- args,
- updater,
- boost::bind(&LLLoginInstance::syncWithUpdater, this, resp, _1, _2));
- }
- }
- else if(reason_response == "mfa_challenge")
- {
- LL_DEBUGS("LLLogin") << " MFA challenge" << LL_ENDL;
-
- if (gViewerWindow)
- {
- gViewerWindow->setShowProgress(false);
- }
-
- showMFAChallange(LLTrans::getString(response["message_id"]));
- }
- else if( reason_response == "key"
- || reason_response == "presence"
- || reason_response == "connect"
- || !message_response.empty() // will be handled in STATE_LOGIN_PROCESS_RESPONSE
- || !response["message_id"].asString().empty()
- )
- {
- // these are events that have already been communicated elsewhere
- attemptComplete();
- }
- else
- {
- LL_WARNS("LLLogin") << "Login failed for an unknown reason: " << LLSDOStreamer<LLSDNotationFormatter>(response) << LL_ENDL;
-
- if (gViewerWindow)
- gViewerWindow->setShowProgress(false);
-
- LLNotificationsUtil::add("LoginFailedUnknown", LLSD::emptyMap(), LLSD::emptyMap(), boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2));
- }
-}
-
-void LLLoginInstance::syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response)
-{
- LL_INFOS("LLLogin") << "LLLoginInstance::syncWithUpdater" << LL_ENDL;
- // 'resp' points to an instance of LLEventAPI::Response that will be
- // destroyed as soon as we return and the notification response functor is
- // unregistered. Modify it so that it tells the updater to go ahead and
- // perform the update. Naturally, if we allowed the user a choice as to
- // whether to proceed or not, this assignment would reflect the user's
- // selection.
- (*resp)["update"] = true;
- attemptComplete();
-}
-
-void LLLoginInstance::handleLoginDisallowed(const LLSD& notification, const LLSD& response)
-{
- attemptComplete();
-}
-
-void LLLoginInstance::handleLoginSuccess(const LLSD& event)
-{
- LL_INFOS("LLLogin") << "LLLoginInstance::handleLoginSuccess" << LL_ENDL;
-
- attemptComplete();
- mRequestData.clear();
-}
-
-void LLLoginInstance::handleDisconnect(const LLSD& event)
-{
- // placeholder
-
- LL_INFOS("LLLogin") << "LLLoginInstance::handleDisconnect placeholder " << LL_ENDL;
-}
-
-void LLLoginInstance::handleIndeterminate(const LLSD& event)
-{
- // The indeterminate response means that the server
- // gave the viewer a new url and params to try.
- // The login module handles the retry, but it gives us the
- // server response so that we may show
- // the user some status.
-
- LLSD message = event.get("data").get("message");
- if(message.isDefined())
- {
- LL_INFOS("LLLogin") << "LLLoginInstance::handleIndeterminate " << message.asString() << LL_ENDL;
-
- LLSD progress_update;
- progress_update["desc"] = message;
- LLEventPumps::getInstance()->obtain("LLProgressView").post(progress_update);
- }
-}
-
-bool LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key)
-{
- if(accepted)
- {
- LL_INFOS("LLLogin") << "LLLoginInstance::handleTOSResponse: accepted " << LL_ENDL;
-
- // Set the request data to true and retry login.
- mRequestData["params"][key] = true;
-
- if (!mRequestData["params"]["token"].asString().empty())
- {
- // SL-18511 this TOS failure happened while we are in the middle of an MFA challenge/response.
- // the previously entered token is very likely expired, so prompt again
- showMFAChallange(LLTrans::getString("LoginFailedAuthenticationMFARequired"));
- }
- else
- {
- reconnect();
- }
- }
- else
- {
- LL_INFOS("LLLogin") << "LLLoginInstance::handleTOSResponse: attemptComplete" << LL_ENDL;
-
- attemptComplete();
- }
-
- LLEventPumps::instance().obtain(TOS_REPLY_PUMP).stopListening(TOS_LISTENER_NAME);
- return true;
-}
-
-void LLLoginInstance::showMFAChallange(const std::string& message)
-{
- LLSD args(llsd::map("MESSAGE", message));
- LLSD payload;
- if (gSavedSettings.getBOOL("RememberUser"))
- {
- LLNotificationsUtil::add("PromptMFATokenWithSave", args, payload,
- boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2));
- }
- else
- {
- LLNotificationsUtil::add("PromptMFAToken", args, payload,
- boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2));
- }
-}
-
-bool LLLoginInstance::handleMFAChallenge(LLSD const & notif, LLSD const & response)
-{
- bool continue_clicked = response["continue"].asBoolean();
- std::string token = response["token"].asString();
- LL_DEBUGS("LLLogin") << "PromptMFAToken: response: " << response << " continue_clicked" << continue_clicked << LL_ENDL;
-
- // strip out whitespace - SL-17034/BUG-231938
- token = boost::regex_replace(token, boost::regex("\\s"), "");
-
- if (continue_clicked && !token.empty())
- {
- LL_INFOS("LLLogin") << "PromptMFAToken: token submitted" << LL_ENDL;
-
- // Set the request data to true and retry login.
- mRequestData["params"]["token"] = token;
- mSaveMFA = response.has("ignore") ? response["ignore"].asBoolean() : false;
- reconnect();
- } else {
- LL_INFOS("LLLogin") << "PromptMFAToken: no token, attemptComplete" << LL_ENDL;
- attemptComplete();
- }
- return true;
-}
-
-std::string construct_start_string()
-{
- std::string start;
- LLSLURL start_slurl = LLStartUp::getStartSLURL();
- switch(start_slurl.getType())
- {
- case LLSLURL::LOCATION:
- {
- // a startup URL was specified
- LLVector3 position = start_slurl.getPosition();
- std::string unescaped_start =
- STRINGIZE( "uri:"
- << start_slurl.getRegion() << "&"
- << position[VX] << "&"
- << position[VY] << "&"
- << position[VZ]);
- start = xml_escape_string(unescaped_start);
- break;
- }
- case LLSLURL::HOME_LOCATION:
- {
- start = "home";
- break;
- }
- default:
- {
- start = "last";
- }
- }
- return start;
-}
-
+/**
+ * @file lllogininstance.cpp
+ * @brief Viewer's host for a login connection.
+ *
+ * $LicenseInfo:firstyear=2009&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "lllogininstance.h"
+
+// llcommon
+#include "llevents.h"
+#include "stringize.h"
+#include "llsdserialize.h"
+
+// llmessage (!)
+#include "llfiltersd2xmlrpc.h" // for xml_escape_string()
+
+// login
+#include "lllogin.h"
+
+// newview
+#include "llhasheduniqueid.h"
+#include "llviewernetwork.h"
+#include "llviewercontrol.h"
+#include "llversioninfo.h"
+#include "llslurl.h"
+#include "llstartup.h"
+#include "llfloaterreg.h"
+#include "llnotifications.h"
+#include "llnotificationsutil.h"
+#include "llwindow.h"
+#include "llviewerwindow.h"
+#include "llprogressview.h"
+#include "llsecapi.h"
+#include "llstartup.h"
+#include "llmachineid.h"
+#include "llevents.h"
+#include "llappviewer.h"
+#include "llsdserialize.h"
+#include "lltrans.h"
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/regex.hpp>
+#include <sstream>
+
+const S32 LOGIN_MAX_RETRIES = 0; // Viewer should not autmatically retry login
+const F32 LOGIN_SRV_TIMEOUT_MIN = 10;
+const F32 LOGIN_SRV_TIMEOUT_MAX = 120;
+const F32 LOGIN_DNS_TIMEOUT_FACTOR = 0.9; // make DNS wait shorter then retry time
+
+class LLLoginInstance::Disposable {
+public:
+ virtual ~Disposable() {}
+};
+
+static const char * const TOS_REPLY_PUMP = "lllogininstance_tos_callback";
+static const char * const TOS_LISTENER_NAME = "lllogininstance_tos";
+
+std::string construct_start_string();
+
+// LLLoginInstance
+//-----------------------------------------------------------------------------
+
+
+LLLoginInstance::LLLoginInstance() :
+ mLoginModule(new LLLogin()),
+ mNotifications(NULL),
+ mLoginState("offline"),
+ mSaveMFA(true),
+ mAttemptComplete(false),
+ mTransferRate(0.0f),
+ mDispatcher("LLLoginInstance", "change")
+{
+ mLoginModule->getEventPump().listen("lllogininstance",
+ boost::bind(&LLLoginInstance::handleLoginEvent, this, _1));
+ // This internal use of LLEventDispatcher doesn't really need
+ // per-function descriptions.
+ mDispatcher.add("fail.login", "", boost::bind(&LLLoginInstance::handleLoginFailure, this, _1));
+ mDispatcher.add("connect", "", boost::bind(&LLLoginInstance::handleLoginSuccess, this, _1));
+ mDispatcher.add("disconnect", "", boost::bind(&LLLoginInstance::handleDisconnect, this, _1));
+ mDispatcher.add("indeterminate", "", boost::bind(&LLLoginInstance::handleIndeterminate, this, _1));
+}
+
+void LLLoginInstance::setPlatformInfo(const std::string platform,
+ const std::string platform_version,
+ const std::string platform_name)
+{
+ mPlatform = platform;
+ mPlatformVersion = platform_version;
+ mPlatformVersionName = platform_name;
+}
+
+LLLoginInstance::~LLLoginInstance()
+{
+}
+
+void LLLoginInstance::connect(LLPointer<LLCredential> credentials)
+{
+ std::vector<std::string> uris;
+ LLGridManager::getInstance()->getLoginURIs(uris);
+ if (uris.size() < 1)
+ {
+ LL_WARNS() << "Failed to get login URIs during connect. No connect for you!" << LL_ENDL;
+ return;
+ }
+ connect(uris.front(), credentials);
+}
+
+void LLLoginInstance::connect(const std::string& uri, LLPointer<LLCredential> credentials)
+{
+ mAttemptComplete = false; // Reset attempt complete at this point!
+ constructAuthParams(credentials);
+ mLoginModule->connect(uri, mRequestData);
+}
+
+void LLLoginInstance::reconnect()
+{
+ // Sort of like connect, only using the pre-existing
+ // request params.
+ std::vector<std::string> uris;
+ LLGridManager::getInstance()->getLoginURIs(uris);
+ mLoginModule->connect(uris.front(), mRequestData);
+ gViewerWindow->setShowProgress(true);
+}
+
+void LLLoginInstance::disconnect()
+{
+ mAttemptComplete = false; // Reset attempt complete at this point!
+ mRequestData.clear();
+ mLoginModule->disconnect();
+}
+
+LLSD LLLoginInstance::getResponse()
+{
+ return mResponseData;
+}
+
+void LLLoginInstance::constructAuthParams(LLPointer<LLCredential> user_credential)
+{
+ // Set up auth request options.
+//#define LL_MINIMIAL_REQUESTED_OPTIONS
+ LLSD requested_options;
+ // *Note: this is where gUserAuth used to be created.
+ requested_options.append("inventory-root");
+ requested_options.append("inventory-skeleton");
+ //requested_options.append("inventory-meat");
+ //requested_options.append("inventory-skel-targets");
+#if (!defined LL_MINIMIAL_REQUESTED_OPTIONS)
+
+ // Not requesting library will trigger mFatalNoLibraryRootFolder
+ requested_options.append("inventory-lib-root");
+ requested_options.append("inventory-lib-owner");
+ requested_options.append("inventory-skel-lib");
+ // requested_options.append("inventory-meat-lib");
+
+ requested_options.append("initial-outfit");
+ requested_options.append("gestures");
+ requested_options.append("display_names");
+ requested_options.append("event_categories");
+ requested_options.append("event_notifications");
+ requested_options.append("classified_categories");
+ requested_options.append("adult_compliant");
+ requested_options.append("buddy-list");
+ requested_options.append("newuser-config");
+ requested_options.append("ui-config");
+
+ //send this info to login.cgi for stats gathering
+ //since viewerstats isn't reliable enough
+ requested_options.append("advanced-mode");
+
+#endif
+ requested_options.append("max-agent-groups");
+ requested_options.append("map-server-url");
+ requested_options.append("voice-config");
+ requested_options.append("tutorial_setting");
+ requested_options.append("login-flags");
+ requested_options.append("global-textures");
+ if(gSavedSettings.getBOOL("ConnectAsGod"))
+ {
+ gSavedSettings.setBOOL("UseDebugMenus", true);
+ requested_options.append("god-connect");
+ }
+
+ LLSD request_params;
+
+ unsigned char hashed_unique_id_string[MD5HEX_STR_SIZE];
+ if ( ! llHashedUniqueID(hashed_unique_id_string) )
+ {
+
+ LL_WARNS("LLLogin") << "Not providing a unique id in request params" << LL_ENDL;
+
+ }
+ request_params["start"] = construct_start_string();
+ request_params["agree_to_tos"] = false; // Always false here. Set true in
+ request_params["read_critical"] = false; // handleTOSResponse
+ request_params["last_exec_event"] = mLastExecEvent;
+ request_params["last_exec_duration"] = mLastExecDuration;
+ request_params["mac"] = (char*)hashed_unique_id_string;
+ request_params["version"] = LLVersionInfo::instance().getVersion();
+ request_params["channel"] = LLVersionInfo::instance().getChannel();
+ request_params["platform"] = mPlatform;
+ request_params["address_size"] = ADDRESS_SIZE;
+ request_params["platform_version"] = mPlatformVersion;
+ request_params["platform_string"] = mPlatformVersionName;
+ request_params["id0"] = mSerialNumber;
+ request_params["host_id"] = gSavedSettings.getString("HostID");
+ request_params["extended_errors"] = true; // request message_id and message_args
+ request_params["token"] = "";
+
+ // log request_params _before_ adding the credentials or sensitive MFA hash data
+ LL_DEBUGS("LLLogin") << "Login parameters: " << LLSDOStreamer<LLSDNotationFormatter>(request_params) << LL_ENDL;
+
+ // Copy the credentials into the request after logging the rest
+ LLSD credentials(user_credential->getLoginParams());
+ for (LLSD::map_const_iterator it = credentials.beginMap();
+ it != credentials.endMap();
+ it++
+ )
+ {
+ request_params[it->first] = it->second;
+ }
+
+ std::string mfa_hash = gSavedSettings.getString("MFAHash"); //non-persistent to enable testing
+ std::string grid(LLGridManager::getInstance()->getGridId());
+ std::string user_id = user_credential->userID();
+ if (gSecAPIHandler)
+ {
+ if (mfa_hash.empty())
+ {
+ // normal execution, mfa_hash was not set from debug setting so load from protected store
+ LLSD data_map = gSecAPIHandler->getProtectedData("mfa_hash", grid);
+ if (data_map.isMap() && data_map.has(user_id))
+ {
+ mfa_hash = data_map[user_id].asString();
+ }
+ }
+ else
+ {
+ // SL-16888 the mfa_hash is being overridden for testing so save it for consistency for future login requests
+ gSecAPIHandler->addToProtectedMap("mfa_hash", grid, user_id, mfa_hash);
+ }
+ }
+ else
+ {
+ LL_WARNS() << "unable to access protected store for mfa_hash" << LL_ENDL;
+ }
+
+ request_params["mfa_hash"] = mfa_hash;
+
+ // Specify desired timeout/retry options
+ LLSD http_params;
+ F32 srv_timeout = llclamp(gSavedSettings.getF32("LoginSRVTimeout"), LOGIN_SRV_TIMEOUT_MIN, LOGIN_SRV_TIMEOUT_MAX);
+ http_params["timeout"] = srv_timeout;
+ http_params["retries"] = LOGIN_MAX_RETRIES;
+ http_params["DNSCacheTimeout"] = srv_timeout * LOGIN_DNS_TIMEOUT_FACTOR; //Default: indefinite
+
+ mRequestData.clear();
+ mRequestData["method"] = "login_to_simulator";
+ mRequestData["params"] = request_params;
+ mRequestData["options"] = requested_options;
+ mRequestData["http_params"] = http_params;
+#if LL_RELEASE_FOR_DOWNLOAD
+ mRequestData["wait_for_updater"] = LLAppViewer::instance()->waitForUpdater();
+#else
+ mRequestData["wait_for_updater"] = false;
+#endif
+}
+
+bool LLLoginInstance::handleLoginEvent(const LLSD& event)
+{
+ LL_DEBUGS("LLLogin") << "LoginListener called!: \n" << event << LL_ENDL;
+
+ if(!(event.has("state") && event.has("change") && event.has("progress")))
+ {
+ LL_ERRS("LLLogin") << "Unknown message from LLLogin: " << event << LL_ENDL;
+ }
+
+ mLoginState = event["state"].asString();
+ mResponseData = event["data"];
+
+ if(event.has("transfer_rate"))
+ {
+ mTransferRate = event["transfer_rate"].asReal();
+ }
+
+ // Call the method registered in constructor, if any, for more specific
+ // handling
+ mDispatcher.try_call(event);
+ return false;
+}
+
+void LLLoginInstance::handleLoginFailure(const LLSD& event)
+{
+ // TODO: we are handling failure in two separate places -
+ // here and in STATE_LOGIN_PROCESS_RESPONSE processing
+ // consider uniting them.
+
+ // Login has failed.
+ // Figure out why and respond...
+ LLSD response = event["data"];
+ LLSD updater = response["updater"];
+
+ // Always provide a response to the updater, if in fact the updater
+ // contacted us, if in fact the ping contains a 'reply' key. Most code
+ // paths tell it not to proceed with updating.
+ ResponsePtr resp(std::make_shared<LLEventAPI::Response>
+ (LLSDMap("update", false), updater));
+
+ std::string reason_response = response["reason"].asString();
+ std::string message_response = response["message"].asString();
+ LL_DEBUGS("LLLogin") << "reason " << reason_response
+ << " message " << message_response
+ << LL_ENDL;
+ // For the cases of critical message or TOS agreement,
+ // start the TOS dialog. The dialog response will be handled
+ // by the LLLoginInstance::handleTOSResponse() callback.
+ // The callback intiates the login attempt next step, either
+ // to reconnect or to end the attempt in failure.
+ if(reason_response == "tos")
+ {
+ LL_INFOS("LLLogin") << " ToS" << LL_ENDL;
+
+ LLSD data(LLSD::emptyMap());
+ data["message"] = message_response;
+ data["reply_pump"] = TOS_REPLY_PUMP;
+ if (gViewerWindow)
+ gViewerWindow->setShowProgress(false);
+ LLFloaterReg::showInstance("message_tos", data);
+ LLEventPumps::instance().obtain(TOS_REPLY_PUMP)
+ .listen(TOS_LISTENER_NAME,
+ boost::bind(&LLLoginInstance::handleTOSResponse,
+ this, _1, "agree_to_tos"));
+ }
+ else if(reason_response == "critical")
+ {
+ LL_INFOS("LLLogin") << "LLLoginInstance::handleLoginFailure Crit" << LL_ENDL;
+
+ LLSD data(LLSD::emptyMap());
+ data["message"] = message_response;
+ data["reply_pump"] = TOS_REPLY_PUMP;
+ if(response.has("error_code"))
+ {
+ data["error_code"] = response["error_code"];
+ }
+ if(response.has("certificate"))
+ {
+ data["certificate"] = response["certificate"];
+ }
+
+ if (gViewerWindow)
+ gViewerWindow->setShowProgress(false);
+
+ LLFloaterReg::showInstance("message_critical", data);
+ LLEventPumps::instance().obtain(TOS_REPLY_PUMP)
+ .listen(TOS_LISTENER_NAME,
+ boost::bind(&LLLoginInstance::handleTOSResponse,
+ this, _1, "read_critical"));
+ }
+ else if(reason_response == "update")
+ {
+ // This can happen if the user clicked Login quickly, before we heard
+ // back from the Viewer Version Manager, but login failed because
+ // login.cgi is insisting on a required update. We were called with an
+ // event that bundles both the login.cgi 'response' and the
+ // synchronization event from the 'updater'.
+ std::string login_version = response["message_args"]["VERSION"];
+ std::string vvm_version = updater["VERSION"];
+ std::string relnotes = updater["URL"];
+ LL_WARNS("LLLogin") << "Login failed because an update to version " << login_version << " is required." << LL_ENDL;
+ // vvm_version might be empty because we might not have gotten
+ // SLVersionChecker's LoginSync handshake. But if it IS populated, it
+ // should (!) be the same as the version we got from login.cgi.
+ if ((! vvm_version.empty()) && vvm_version != login_version)
+ {
+ LL_WARNS("LLLogin") << "VVM update version " << vvm_version
+ << " differs from login version " << login_version
+ << "; presenting VVM version to match release notes URL"
+ << LL_ENDL;
+ login_version = vvm_version;
+ }
+ if (relnotes.empty() || relnotes.find("://") == std::string::npos)
+ {
+ relnotes = LLTrans::getString("RELEASE_NOTES_BASE_URL");
+ if (!LLStringUtil::endsWith(relnotes, "/"))
+ relnotes += "/";
+ relnotes += LLURI::escape(login_version) + ".html";
+ }
+
+ if (gViewerWindow)
+ gViewerWindow->setShowProgress(false);
+
+ LLSD args;
+ args["VERSION"] = login_version;
+ args["URL"] = relnotes;
+
+ if (updater.isUndefined())
+ {
+ // If the updater failed to shake hands, better advise the user to
+ // download the update him/herself.
+ LLNotificationsUtil::add(
+ "RequiredUpdate",
+ args,
+ updater,
+ boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2));
+ }
+ else
+ {
+ // If we've heard from the updater that an update is required,
+ // then display the prompt that assures the user we'll take care
+ // of it. This is the one case in which we bind 'resp':
+ // instead of destroying our Response object (and thus sending a
+ // negative reply to the updater) as soon as we exit this
+ // function, bind our shared_ptr so it gets passed into
+ // syncWithUpdater. That ensures that the response is delayed
+ // until the user has responded to the notification.
+ LLNotificationsUtil::add(
+ "PauseForUpdate",
+ args,
+ updater,
+ boost::bind(&LLLoginInstance::syncWithUpdater, this, resp, _1, _2));
+ }
+ }
+ else if(reason_response == "mfa_challenge")
+ {
+ LL_DEBUGS("LLLogin") << " MFA challenge" << LL_ENDL;
+
+ if (gViewerWindow)
+ {
+ gViewerWindow->setShowProgress(false);
+ }
+
+ showMFAChallange(LLTrans::getString(response["message_id"]));
+ }
+ else if( reason_response == "key"
+ || reason_response == "presence"
+ || reason_response == "connect"
+ || !message_response.empty() // will be handled in STATE_LOGIN_PROCESS_RESPONSE
+ || !response["message_id"].asString().empty()
+ )
+ {
+ // these are events that have already been communicated elsewhere
+ attemptComplete();
+ }
+ else
+ {
+ LL_WARNS("LLLogin") << "Login failed for an unknown reason: " << LLSDOStreamer<LLSDNotationFormatter>(response) << LL_ENDL;
+
+ if (gViewerWindow)
+ gViewerWindow->setShowProgress(false);
+
+ LLNotificationsUtil::add("LoginFailedUnknown", LLSD::emptyMap(), LLSD::emptyMap(), boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2));
+ }
+}
+
+void LLLoginInstance::syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response)
+{
+ LL_INFOS("LLLogin") << "LLLoginInstance::syncWithUpdater" << LL_ENDL;
+ // 'resp' points to an instance of LLEventAPI::Response that will be
+ // destroyed as soon as we return and the notification response functor is
+ // unregistered. Modify it so that it tells the updater to go ahead and
+ // perform the update. Naturally, if we allowed the user a choice as to
+ // whether to proceed or not, this assignment would reflect the user's
+ // selection.
+ (*resp)["update"] = true;
+ attemptComplete();
+}
+
+void LLLoginInstance::handleLoginDisallowed(const LLSD& notification, const LLSD& response)
+{
+ attemptComplete();
+}
+
+void LLLoginInstance::handleLoginSuccess(const LLSD& event)
+{
+ LL_INFOS("LLLogin") << "LLLoginInstance::handleLoginSuccess" << LL_ENDL;
+
+ attemptComplete();
+ mRequestData.clear();
+}
+
+void LLLoginInstance::handleDisconnect(const LLSD& event)
+{
+ // placeholder
+
+ LL_INFOS("LLLogin") << "LLLoginInstance::handleDisconnect placeholder " << LL_ENDL;
+}
+
+void LLLoginInstance::handleIndeterminate(const LLSD& event)
+{
+ // The indeterminate response means that the server
+ // gave the viewer a new url and params to try.
+ // The login module handles the retry, but it gives us the
+ // server response so that we may show
+ // the user some status.
+
+ LLSD message = event.get("data").get("message");
+ if(message.isDefined())
+ {
+ LL_INFOS("LLLogin") << "LLLoginInstance::handleIndeterminate " << message.asString() << LL_ENDL;
+
+ LLSD progress_update;
+ progress_update["desc"] = message;
+ LLEventPumps::getInstance()->obtain("LLProgressView").post(progress_update);
+ }
+}
+
+bool LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key)
+{
+ if(accepted)
+ {
+ LL_INFOS("LLLogin") << "LLLoginInstance::handleTOSResponse: accepted " << LL_ENDL;
+
+ // Set the request data to true and retry login.
+ mRequestData["params"][key] = true;
+
+ if (!mRequestData["params"]["token"].asString().empty())
+ {
+ // SL-18511 this TOS failure happened while we are in the middle of an MFA challenge/response.
+ // the previously entered token is very likely expired, so prompt again
+ showMFAChallange(LLTrans::getString("LoginFailedAuthenticationMFARequired"));
+ }
+ else
+ {
+ reconnect();
+ }
+ }
+ else
+ {
+ LL_INFOS("LLLogin") << "LLLoginInstance::handleTOSResponse: attemptComplete" << LL_ENDL;
+
+ attemptComplete();
+ }
+
+ LLEventPumps::instance().obtain(TOS_REPLY_PUMP).stopListening(TOS_LISTENER_NAME);
+ return true;
+}
+
+void LLLoginInstance::showMFAChallange(const std::string& message)
+{
+ LLSD args(llsd::map("MESSAGE", message));
+ LLSD payload;
+ if (gSavedSettings.getBOOL("RememberUser"))
+ {
+ LLNotificationsUtil::add("PromptMFATokenWithSave", args, payload,
+ boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2));
+ }
+ else
+ {
+ LLNotificationsUtil::add("PromptMFAToken", args, payload,
+ boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2));
+ }
+}
+
+bool LLLoginInstance::handleMFAChallenge(LLSD const & notif, LLSD const & response)
+{
+ bool continue_clicked = response["continue"].asBoolean();
+ std::string token = response["token"].asString();
+ LL_DEBUGS("LLLogin") << "PromptMFAToken: response: " << response << " continue_clicked" << continue_clicked << LL_ENDL;
+
+ // strip out whitespace - SL-17034/BUG-231938
+ token = boost::regex_replace(token, boost::regex("\\s"), "");
+
+ if (continue_clicked && !token.empty())
+ {
+ LL_INFOS("LLLogin") << "PromptMFAToken: token submitted" << LL_ENDL;
+
+ // Set the request data to true and retry login.
+ mRequestData["params"]["token"] = token;
+ mSaveMFA = response.has("ignore") ? response["ignore"].asBoolean() : false;
+ reconnect();
+ } else {
+ LL_INFOS("LLLogin") << "PromptMFAToken: no token, attemptComplete" << LL_ENDL;
+ attemptComplete();
+ }
+ return true;
+}
+
+std::string construct_start_string()
+{
+ std::string start;
+ LLSLURL start_slurl = LLStartUp::getStartSLURL();
+ switch(start_slurl.getType())
+ {
+ case LLSLURL::LOCATION:
+ {
+ // a startup URL was specified
+ LLVector3 position = start_slurl.getPosition();
+ std::string unescaped_start =
+ STRINGIZE( "uri:"
+ << start_slurl.getRegion() << "&"
+ << position[VX] << "&"
+ << position[VY] << "&"
+ << position[VZ]);
+ start = xml_escape_string(unescaped_start);
+ break;
+ }
+ case LLSLURL::HOME_LOCATION:
+ {
+ start = "home";
+ break;
+ }
+ default:
+ {
+ start = "last";
+ }
+ }
+ return start;
+}
+