PageRenderTime 66ms CodeModel.GetById 27ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/viewer_components/login/lllogin.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 434 lines | 205 code | 51 blank | 178 comment | 26 complexity | 059177884f22fb777df626707c51615a MD5 | raw file
  1/** 
  2 * @file lllogin.cpp
  3 *
  4 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
  5 * Second Life Viewer Source Code
  6 * Copyright (C) 2010, Linden Research, Inc.
  7 * 
  8 * This library is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU Lesser General Public
 10 * License as published by the Free Software Foundation;
 11 * version 2.1 of the License only.
 12 * 
 13 * This library is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 16 * Lesser General Public License for more details.
 17 * 
 18 * You should have received a copy of the GNU Lesser General Public
 19 * License along with this library; if not, write to the Free Software
 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 21 * 
 22 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 23 * $/LicenseInfo$
 24 */
 25
 26#include <boost/coroutine/coroutine.hpp>
 27#include "linden_common.h"
 28#include "llsd.h"
 29#include "llsdutil.h"
 30
 31/*==========================================================================*|
 32#ifdef LL_WINDOWS
 33	// non-virtual destructor warning, boost::statechart does this intentionally.
 34	#pragma warning (disable : 4265) 
 35#endif
 36|*==========================================================================*/
 37
 38#include "lllogin.h"
 39
 40#include <boost/bind.hpp>
 41
 42#include "llcoros.h"
 43#include "llevents.h"
 44#include "lleventfilter.h"
 45#include "lleventcoro.h"
 46
 47//*********************
 48// LLLogin
 49// *NOTE:Mani - Is this Impl needed now that the state machine runs the show?
 50class LLLogin::Impl
 51{
 52public:
 53    Impl():
 54		mPump("login", true) // Create the module's event pump with a tweaked (unique) name.
 55    {
 56        mValidAuthResponse["status"]        = LLSD();
 57        mValidAuthResponse["errorcode"]     = LLSD();
 58        mValidAuthResponse["error"]         = LLSD();
 59        mValidAuthResponse["transfer_rate"] = LLSD();
 60    }
 61
 62    void connect(const std::string& uri, const LLSD& credentials);
 63    void disconnect();
 64	LLEventPump& getEventPump() { return mPump; }
 65
 66private:
 67	LLSD getProgressEventLLSD(const std::string& state, const std::string& change,
 68						   const LLSD& data = LLSD())
 69	{
 70		LLSD status_data;
 71		status_data["state"] = state;
 72		status_data["change"] = change;
 73		status_data["progress"] = 0.0f;
 74
 75		if(mAuthResponse.has("transfer_rate"))
 76		{
 77			status_data["transfer_rate"] = mAuthResponse["transfer_rate"];
 78		}
 79
 80		if(data.isDefined())
 81		{
 82			status_data["data"] = data;
 83		}
 84		return status_data;
 85	}
 86
 87	void sendProgressEvent(const std::string& state, const std::string& change,
 88						   const LLSD& data = LLSD())
 89	{
 90		LLSD status_data = getProgressEventLLSD(state, change, data);
 91		mPump.post(status_data);
 92	}
 93
 94    LLSD validateResponse(const std::string& pumpName, const LLSD& response)
 95    {
 96        // Validate the response. If we don't recognize it, things
 97        // could get ugly.
 98        std::string mismatch(llsd_matches(mValidAuthResponse, response));
 99        if (! mismatch.empty())
100        {
101            LL_ERRS("LLLogin") << "Received unrecognized event (" << mismatch << ") on "
102                               << pumpName << "pump: " << response
103                               << LL_ENDL;
104            return LLSD();
105        }
106
107        return response;
108    }
109
110    // In a coroutine's top-level function args, do NOT NOT NOT accept
111    // references (const or otherwise) to anything but the self argument! Pass
112    // by value only!
113    void login_(LLCoros::self& self, std::string uri, LLSD credentials);
114
115    LLEventStream mPump;
116	LLSD mAuthResponse, mValidAuthResponse;
117};
118
119void LLLogin::Impl::connect(const std::string& uri, const LLSD& login_params)
120{
121    LL_DEBUGS("LLLogin") << " connect with  uri '" << uri << "', login_params " << login_params << LL_ENDL;
122	
123    // Launch a coroutine with our login_() method. Run the coroutine until
124    // its first wait; at that point, return here.
125    std::string coroname = 
126        LLCoros::instance().launch("LLLogin::Impl::login_",
127                                   boost::bind(&Impl::login_, this, _1, uri, login_params));
128    LL_DEBUGS("LLLogin") << " connected with  uri '" << uri << "', login_params " << login_params << LL_ENDL;	
129}
130
131void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD login_params)
132{
133	try
134	{
135	LLSD printable_params = login_params;
136	//if(printable_params.has("params") 
137	//	&& printable_params["params"].has("passwd")) 
138	//{
139	//	printable_params["params"]["passwd"] = "*******";
140	//}
141    LL_DEBUGS("LLLogin") << "Entering coroutine " << LLCoros::instance().getName(self)
142                        << " with uri '" << uri << "', parameters " << printable_params << LL_ENDL;
143
144	// Arriving in SRVRequest state
145    LLEventStream replyPump("SRVreply", true);
146    // Should be an array of one or more uri strings.
147
148    LLSD rewrittenURIs;
149    {
150        LLEventTimeout filter(replyPump);
151        sendProgressEvent("offline", "srvrequest");
152
153        // Request SRV record.
154        LL_DEBUGS("LLLogin") << "Requesting SRV record from " << uri << LL_ENDL;
155
156        // *NOTE:Mani - Completely arbitrary default timeout value for SRV request.
157		F32 seconds_to_timeout = 5.0f;
158		if(login_params.has("cfg_srv_timeout"))
159		{
160			seconds_to_timeout = login_params["cfg_srv_timeout"].asReal();
161		}
162
163        // If the SRV request times out (e.g. EXT-3934), simulate response: an
164        // array containing our original URI.
165        LLSD fakeResponse(LLSD::emptyArray());
166        fakeResponse.append(uri);
167		filter.eventAfter(seconds_to_timeout, fakeResponse);
168
169		std::string srv_pump_name = "LLAres";
170		if(login_params.has("cfg_srv_pump"))
171		{
172			srv_pump_name = login_params["cfg_srv_pump"].asString();
173		}
174
175		// Make request
176        LLSD request;
177        request["op"] = "rewriteURI";
178        request["uri"] = uri;
179        request["reply"] = replyPump.getName();
180        rewrittenURIs = postAndWait(self, request, srv_pump_name, filter);
181        // EXP-772: If rewrittenURIs fail, try original URI as a fallback.
182        rewrittenURIs.append(uri);
183    } // we no longer need the filter
184
185    LLEventPump& xmlrpcPump(LLEventPumps::instance().obtain("LLXMLRPCTransaction"));
186    // EXT-4193: use a DIFFERENT reply pump than for the SRV request. We used
187    // to share them -- but the EXT-3934 fix made it possible for an abandoned
188    // SRV response to arrive just as we were expecting the XMLRPC response.
189    LLEventStream loginReplyPump("loginreply", true);
190
191    // Loop through the rewrittenURIs, counting attempts along the way.
192    // Because of possible redirect responses, we may make more than one
193    // attempt per rewrittenURIs entry.
194    LLSD::Integer attempts = 0;
195    for (LLSD::array_const_iterator urit(rewrittenURIs.beginArray()),
196             urend(rewrittenURIs.endArray());
197         urit != urend; ++urit)
198    {
199        LLSD request(login_params);
200        request["reply"] = loginReplyPump.getName();
201        request["uri"] = *urit;
202        std::string status;
203
204        // Loop back to here if login attempt redirects to a different
205        // request["uri"]
206        for (;;)
207        {
208            ++attempts;
209            LLSD progress_data;
210            progress_data["attempt"] = attempts;
211            progress_data["request"] = request;
212			if(progress_data["request"].has("params")
213				&& progress_data["request"]["params"].has("passwd"))
214			{
215				progress_data["request"]["params"]["passwd"] = "*******";
216			}
217            sendProgressEvent("offline", "authenticating", progress_data);
218
219            // We expect zero or more "Downloading" status events, followed by
220            // exactly one event with some other status. Use postAndWait() the
221            // first time, because -- at least in unit-test land -- it's
222            // possible for the reply to arrive before the post() call
223            // returns. Subsequent responses, of course, must be awaited
224            // without posting again.
225            for (mAuthResponse = validateResponse(loginReplyPump.getName(),
226                                 postAndWait(self, request, xmlrpcPump, loginReplyPump, "reply"));
227                 mAuthResponse["status"].asString() == "Downloading";
228                 mAuthResponse = validateResponse(loginReplyPump.getName(),
229                                     waitForEventOn(self, loginReplyPump)))
230            {
231                // Still Downloading -- send progress update.
232                sendProgressEvent("offline", "downloading");
233            }
234				 
235			LL_DEBUGS("LLLogin") << "Auth Response: " << mAuthResponse << LL_ENDL;
236            status = mAuthResponse["status"].asString();
237
238            // Okay, we've received our final status event for this
239            // request. Unless we got a redirect response, break the retry
240            // loop for the current rewrittenURIs entry.
241            if (!(status == "Complete" &&
242                  mAuthResponse["responses"]["login"].asString() == "indeterminate"))
243            {
244                break;
245            }
246
247			sendProgressEvent("offline", "indeterminate", mAuthResponse["responses"]);
248
249            // Here the login service at the current URI is redirecting us
250            // to some other URI ("indeterminate" -- why not "redirect"?).
251            // The response should contain another uri to try, with its
252            // own auth method.
253            request["uri"] = mAuthResponse["responses"]["next_url"].asString();
254            request["method"] = mAuthResponse["responses"]["next_method"].asString();
255        } // loop back to try the redirected URI
256
257        // Here we're done with redirects for the current rewrittenURIs
258        // entry.
259        if (status == "Complete")
260        {
261            // StatusComplete does not imply auth success. Check the
262            // actual outcome of the request. We've already handled the
263            // "indeterminate" case in the loop above.
264            if (mAuthResponse["responses"]["login"].asString() == "true")
265            {
266                sendProgressEvent("online", "connect", mAuthResponse["responses"]);
267            }
268            else
269            {
270                sendProgressEvent("offline", "fail.login", mAuthResponse["responses"]);
271            }
272            return;             // Done!
273        }
274        // If we don't recognize status at all, trouble
275        if (! (status == "CURLError"
276               || status == "XMLRPCError"
277               || status == "OtherError"))
278        {
279            LL_ERRS("LLLogin") << "Unexpected status from " << xmlrpcPump.getName() << " pump: "
280                               << mAuthResponse << LL_ENDL;
281            return;
282        }
283
284        // Here status IS one of the errors tested above.
285    } // Retry if there are any more rewrittenURIs.
286
287    // Here we got through all the rewrittenURIs without succeeding. Tell
288    // caller this didn't work out so well. Of course, the only failure data
289    // we can reasonably show are from the last of the rewrittenURIs.
290
291	// *NOTE: The response from LLXMLRPCListener's Poller::poll method returns an
292	// llsd with no "responses" node. To make the output from an incomplete login symmetrical 
293	// to success, add a data/message and data/reason fields.
294	LLSD error_response;
295	error_response["reason"] = mAuthResponse["status"];
296	error_response["errorcode"] = mAuthResponse["errorcode"];
297	error_response["message"] = mAuthResponse["error"];
298	if(mAuthResponse.has("certificate"))
299	{
300		error_response["certificate"] = mAuthResponse["certificate"];
301	}
302	sendProgressEvent("offline", "fail.login", error_response);
303	}
304	catch (...) {
305		llerrs << "login exception caught" << llendl; 
306	}
307}
308
309void LLLogin::Impl::disconnect()
310{
311    sendProgressEvent("offline", "disconnect");
312}
313
314//*********************
315// LLLogin
316LLLogin::LLLogin() :
317	mImpl(new LLLogin::Impl())
318{
319}
320
321LLLogin::~LLLogin()
322{
323}
324
325void LLLogin::connect(const std::string& uri, const LLSD& credentials)
326{
327	mImpl->connect(uri, credentials);
328}
329
330
331void LLLogin::disconnect()
332{
333	mImpl->disconnect();
334}
335
336LLEventPump& LLLogin::getEventPump()
337{
338	return mImpl->getEventPump();
339}
340
341// The following is the list of important functions that happen in the 
342// current login process that we want to move to this login module.
343
344// The list associates to event with the original idle_startup() 'STATE'.
345
346// Rewrite URIs
347 // State_LOGIN_AUTH_INIT
348// Given a vector of login uris (usually just one), perform a dns lookup for the 
349// SRV record from each URI. I think this is used to distribute login requests to 
350// a single URI to multiple hosts.
351// This is currently a synchronous action. (See LLSRV::rewriteURI() implementation)
352// On dns lookup error the output uris == the input uris.
353//
354// Input: A vector of login uris
355// Output: A vector of login uris
356//
357// Code:
358// std::vector<std::string> uris;
359// LLViewerLogin::getInstance()->getLoginURIs(uris);
360// std::vector<std::string>::const_iterator iter, end;
361// for (iter = uris.begin(), end = uris.end(); iter != end; ++iter)
362// {
363//	std::vector<std::string> rewritten;
364//	rewritten = LLSRV::rewriteURI(*iter);
365//	sAuthUris.insert(sAuthUris.end(),
366//					 rewritten.begin(), rewritten.end());
367// }
368// sAuthUriNum = 0;
369
370// Authenticate 
371// STATE_LOGIN_AUTHENTICATE
372// Connect to the login server, presumably login.cgi, requesting the login 
373// and a slew of related initial connection information.
374// This is an asynch action. The final response, whether success or error
375// is handled by STATE_LOGIN_PROCESS_REPONSE.
376// There is no immediate error or output from this call.
377// 
378// Input: 
379//  URI
380//  Credentials (first, last, password)
381//  Start location
382//  Bool Flags:
383//    skip optional update
384//    accept terms of service
385//    accept critical message
386//  Last exec event. (crash state of previous session)
387//  requested optional data (inventory skel, initial outfit, etc.)
388//  local mac address
389//  viewer serial no. (md5 checksum?)
390
391//sAuthUriNum = llclamp(sAuthUriNum, 0, (S32)sAuthUris.size()-1);
392//LLUserAuth::getInstance()->authenticate(
393//	sAuthUris[sAuthUriNum],
394//	auth_method,
395//	firstname,
396//	lastname,			
397//	password, // web_login_key,
398//	start.str(),
399//	gSkipOptionalUpdate,
400//	gAcceptTOS,
401//	gAcceptCriticalMessage,
402//	gLastExecEvent,
403//	requested_options,
404//	hashed_mac_string,
405//	LLAppViewer::instance()->getSerialNumber());
406
407//
408// Download the Response
409// STATE_LOGIN_NO_REPONSE_YET and STATE_LOGIN_DOWNLOADING
410// I had assumed that this was default behavior of the message system. However...
411// During login, the message system is checked only by these two states in idle_startup().
412// I guess this avoids the overhead of checking network messages for those login states
413// that don't need to do so, but geez!
414// There are two states to do this one function just to update the login
415// status text from 'Logging In...' to 'Downloading...'
416// 
417
418//
419// Handle Login Response
420// STATE_LOGIN_PROCESS_RESPONSE
421// 
422// This state handle the result of the request to login. There is a metric ton of
423// code in this case. This state will transition to:
424// STATE_WORLD_INIT, on success.
425// STATE_AUTHENTICATE, on failure.
426// STATE_UPDATE_CHECK, to handle user during login interaction like TOS display.
427//
428// Much of the code in this case belongs on the viewer side of the fence and not in login. 
429// Login should probably return with a couple of events, success and failure.
430// Failure conditions can be specified in the events data pacet to allow the viewer 
431// to re-engauge login as is appropriate. (Or should there be multiple failure messages?)
432// Success is returned with the data requested from the login. According to OGP specs 
433// there may be intermediate steps before reaching this result in future login 
434// implementations.