/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Net; using System.Reflection; using System.Text.RegularExpressions; using System.Threading; using System.Web; using log4net; using Nwc.XmlRpc; using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Framework.Communications.Cache; using OpenSim.Framework.Statistics; using OpenSim.Services.Interfaces; namespace OpenSim.Framework.Communications.Services { public abstract class LoginService { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); protected string m_welcomeMessage = "Welcome to OpenSim"; protected int m_minLoginLevel = 0; protected UserManagerBase m_userManager = null; protected Mutex m_loginMutex = new Mutex(false); /// /// Used during login to send the skeleton of the OpenSim Library to the client. /// protected LibraryRootFolder m_libraryRootFolder; protected uint m_defaultHomeX; protected uint m_defaultHomeY; protected bool m_warn_already_logged = true; /// /// Used by the login service to make requests to the inventory service. /// protected IInterServiceInventoryServices m_interInventoryService; // Hack protected IInventoryService m_InventoryService; /// /// Constructor /// /// /// /// public LoginService(UserManagerBase userManager, LibraryRootFolder libraryRootFolder, string welcomeMess) { m_userManager = userManager; m_libraryRootFolder = libraryRootFolder; if (welcomeMess != String.Empty) { m_welcomeMessage = welcomeMess; } } /// /// Called when we receive the client's initial XMLRPC login_to_simulator request message /// /// The XMLRPC request /// The response to send public virtual XmlRpcResponse XmlRpcLoginMethod(XmlRpcRequest request, IPEndPoint remoteClient) { // Temporary fix m_loginMutex.WaitOne(); try { //CFK: CustomizeResponse contains sufficient strings to alleviate the need for this. //CKF: m_log.Info("[LOGIN]: Attempting login now..."); XmlRpcResponse response = new XmlRpcResponse(); Hashtable requestData = (Hashtable)request.Params[0]; SniffLoginKey((Uri)request.Params[2], requestData); bool GoodXML = (requestData.Contains("first") && requestData.Contains("last") && (requestData.Contains("passwd") || requestData.Contains("web_login_key"))); string startLocationRequest = "last"; UserProfileData userProfile; LoginResponse logResponse = new LoginResponse(); string firstname; string lastname; if (GoodXML) { if (requestData.Contains("start")) { startLocationRequest = (string)requestData["start"]; } firstname = (string)requestData["first"]; lastname = (string)requestData["last"]; m_log.InfoFormat( "[LOGIN BEGIN]: XMLRPC Received login request message from user '{0}' '{1}'", firstname, lastname); string clientVersion = "Unknown"; if (requestData.Contains("version")) { clientVersion = (string)requestData["version"]; } m_log.DebugFormat( "[LOGIN]: XMLRPC Client is {0}, start location is {1}", clientVersion, startLocationRequest); if (!TryAuthenticateXmlRpcLogin(request, firstname, lastname, out userProfile)) { return logResponse.CreateLoginFailedResponse(); } } else { m_log.Info( "[LOGIN END]: XMLRPC login_to_simulator login message did not contain all the required data"); return logResponse.CreateGridErrorResponse(); } if (userProfile.GodLevel < m_minLoginLevel) { return logResponse.CreateLoginBlockedResponse(); } else { // If we already have a session... if (userProfile.CurrentAgent != null && userProfile.CurrentAgent.AgentOnline) { //TODO: The following statements can cause trouble: // If agentOnline could not turn from true back to false normally // because of some problem, for instance, the crashment of server or client, // the user cannot log in any longer. userProfile.CurrentAgent.AgentOnline = false; m_userManager.CommitAgent(ref userProfile); // try to tell the region that their user is dead. LogOffUser(userProfile, " XMLRPC You were logged off because you logged in from another location"); if (m_warn_already_logged) { // This is behavior for for grid, reject login m_log.InfoFormat( "[LOGIN END]: XMLRPC Notifying user {0} {1} that they are already logged in", firstname, lastname); return logResponse.CreateAlreadyLoggedInResponse(); } else { // This is behavior for standalone (silent logout of last hung session) m_log.InfoFormat( "[LOGIN]: XMLRPC User {0} {1} is already logged in, not notifying user, kicking old presence and starting new login.", firstname, lastname); } } // Otherwise... // Create a new agent session // XXYY we don't need this //m_userManager.ResetAttachments(userProfile.ID); CreateAgent(userProfile, request); // We need to commit the agent right here, even though the userProfile info is not complete // at this point. There is another commit further down. // This is for the new sessionID to be stored so that the region can check it for session authentication. // CustomiseResponse->PrepareLoginToRegion CommitAgent(ref userProfile); try { UUID agentID = userProfile.ID; InventoryData inventData = null; try { inventData = GetInventorySkeleton(agentID); } catch (Exception e) { m_log.ErrorFormat( "[LOGIN END]: Error retrieving inventory skeleton of agent {0} - {1}", agentID, e); // Let's not panic if (!AllowLoginWithoutInventory()) return logResponse.CreateLoginInventoryFailedResponse(); } if (inventData != null) { ArrayList AgentInventoryArray = inventData.InventoryArray; Hashtable InventoryRootHash = new Hashtable(); InventoryRootHash["folder_id"] = inventData.RootFolderID.ToString(); ArrayList InventoryRoot = new ArrayList(); InventoryRoot.Add(InventoryRootHash); logResponse.InventoryRoot = InventoryRoot; logResponse.InventorySkeleton = AgentInventoryArray; } // Inventory Library Section Hashtable InventoryLibRootHash = new Hashtable(); InventoryLibRootHash["folder_id"] = "00000112-000f-0000-0000-000100bba000"; ArrayList InventoryLibRoot = new ArrayList(); InventoryLibRoot.Add(InventoryLibRootHash); logResponse.InventoryLibRoot = InventoryLibRoot; logResponse.InventoryLibraryOwner = GetLibraryOwner(); logResponse.InventoryLibrary = GetInventoryLibrary(); logResponse.CircuitCode = Util.RandomClass.Next(); logResponse.Lastname = userProfile.SurName; logResponse.Firstname = userProfile.FirstName; logResponse.AgentID = agentID; logResponse.SessionID = userProfile.CurrentAgent.SessionID; logResponse.SecureSessionID = userProfile.CurrentAgent.SecureSessionID; logResponse.Message = GetMessage(); logResponse.BuddList = ConvertFriendListItem(m_userManager.GetUserFriendList(agentID)); logResponse.StartLocation = startLocationRequest; if (CustomiseResponse(logResponse, userProfile, startLocationRequest, remoteClient)) { userProfile.LastLogin = userProfile.CurrentAgent.LoginTime; CommitAgent(ref userProfile); // If we reach this point, then the login has successfully logged onto the grid if (StatsManager.UserStats != null) StatsManager.UserStats.AddSuccessfulLogin(); m_log.DebugFormat( "[LOGIN END]: XMLRPC Authentication of user {0} {1} successful. Sending response to client.", firstname, lastname); return logResponse.ToXmlRpcResponse(); } else { m_log.ErrorFormat("[LOGIN END]: XMLRPC informing user {0} {1} that login failed due to an unavailable region", firstname, lastname); return logResponse.CreateDeadRegionResponse(); } } catch (Exception e) { m_log.Error("[LOGIN END]: XMLRPC Login failed, " + e); m_log.Error(e.StackTrace); } } m_log.Info("[LOGIN END]: XMLRPC Login failed. Sending back blank XMLRPC response"); return response; } finally { m_loginMutex.ReleaseMutex(); } } protected virtual bool TryAuthenticateXmlRpcLogin( XmlRpcRequest request, string firstname, string lastname, out UserProfileData userProfile) { Hashtable requestData = (Hashtable)request.Params[0]; userProfile = GetTheUser(firstname, lastname); if (userProfile == null) { m_log.Debug("[LOGIN END]: XMLRPC Could not find a profile for " + firstname + " " + lastname); return false; } else { if (requestData.Contains("passwd")) { string passwd = (string)requestData["passwd"]; bool authenticated = AuthenticateUser(userProfile, passwd); if (!authenticated) m_log.DebugFormat("[LOGIN END]: XMLRPC User {0} {1} failed password authentication", firstname, lastname); return authenticated; } if (requestData.Contains("web_login_key")) { try { UUID webloginkey = new UUID((string)requestData["web_login_key"]); bool authenticated = AuthenticateUser(userProfile, webloginkey); if (!authenticated) m_log.DebugFormat("[LOGIN END]: XMLRPC User {0} {1} failed web login key authentication", firstname, lastname); return authenticated; } catch (Exception e) { m_log.DebugFormat( "[LOGIN END]: XMLRPC Bad web_login_key: {0} for user {1} {2}, exception {3}", requestData["web_login_key"], firstname, lastname, e); return false; } } m_log.DebugFormat( "[LOGIN END]: XMLRPC login request for {0} {1} contained neither a password nor a web login key", firstname, lastname); } return false; } protected virtual bool TryAuthenticateLLSDLogin(string firstname, string lastname, string passwd, out UserProfileData userProfile) { bool GoodLogin = false; userProfile = GetTheUser(firstname, lastname); if (userProfile == null) { m_log.Info("[LOGIN]: LLSD Could not find a profile for " + firstname + " " + lastname); return false; } GoodLogin = AuthenticateUser(userProfile, passwd); return GoodLogin; } /// /// Called when we receive the client's initial LLSD login_to_simulator request message /// /// The LLSD request /// The response to send public OSD LLSDLoginMethod(OSD request, IPEndPoint remoteClient) { // Temporary fix m_loginMutex.WaitOne(); try { // bool GoodLogin = false; string startLocationRequest = "last"; UserProfileData userProfile = null; LoginResponse logResponse = new LoginResponse(); if (request.Type == OSDType.Map) { OSDMap map = (OSDMap)request; if (map.ContainsKey("first") && map.ContainsKey("last") && map.ContainsKey("passwd")) { string firstname = map["first"].AsString(); string lastname = map["last"].AsString(); string passwd = map["passwd"].AsString(); if (map.ContainsKey("start")) { m_log.Info("[LOGIN]: LLSD StartLocation Requested: " + map["start"].AsString()); startLocationRequest = map["start"].AsString(); } m_log.Info("[LOGIN]: LLSD Login Requested for: '" + firstname + "' '" + lastname + "' / " + passwd); if (!TryAuthenticateLLSDLogin(firstname, lastname, passwd, out userProfile)) { return logResponse.CreateLoginFailedResponseLLSD(); } } else return logResponse.CreateLoginFailedResponseLLSD(); } else return logResponse.CreateLoginFailedResponseLLSD(); if (userProfile.GodLevel < m_minLoginLevel) { return logResponse.CreateLoginBlockedResponseLLSD(); } else { // If we already have a session... if (userProfile.CurrentAgent != null && userProfile.CurrentAgent.AgentOnline) { userProfile.CurrentAgent.AgentOnline = false; m_userManager.CommitAgent(ref userProfile); // try to tell the region that their user is dead. LogOffUser(userProfile, " LLSD You were logged off because you logged in from another location"); if (m_warn_already_logged) { // This is behavior for for grid, reject login m_log.InfoFormat( "[LOGIN END]: LLSD Notifying user {0} {1} that they are already logged in", userProfile.FirstName, userProfile.SurName); userProfile.CurrentAgent = null; return logResponse.CreateAlreadyLoggedInResponseLLSD(); } else { // This is behavior for standalone (silent logout of last hung session) m_log.InfoFormat( "[LOGIN]: LLSD User {0} {1} is already logged in, not notifying user, kicking old presence and starting new login.", userProfile.FirstName, userProfile.SurName); } } // Otherwise... // Create a new agent session // XXYY We don't need this //m_userManager.ResetAttachments(userProfile.ID); CreateAgent(userProfile, request); // We need to commit the agent right here, even though the userProfile info is not complete // at this point. There is another commit further down. // This is for the new sessionID to be stored so that the region can check it for session authentication. // CustomiseResponse->PrepareLoginToRegion CommitAgent(ref userProfile); try { UUID agentID = userProfile.ID; //InventoryData inventData = GetInventorySkeleton(agentID); InventoryData inventData = null; try { inventData = GetInventorySkeleton(agentID); } catch (Exception e) { m_log.ErrorFormat( "[LOGIN END]: LLSD Error retrieving inventory skeleton of agent {0}, {1} - {2}", agentID, e.GetType(), e.Message); return logResponse.CreateLoginFailedResponseLLSD();// .CreateLoginInventoryFailedResponseLLSD (); } ArrayList AgentInventoryArray = inventData.InventoryArray; Hashtable InventoryRootHash = new Hashtable(); InventoryRootHash["folder_id"] = inventData.RootFolderID.ToString(); ArrayList InventoryRoot = new ArrayList(); InventoryRoot.Add(InventoryRootHash); // Inventory Library Section Hashtable InventoryLibRootHash = new Hashtable(); InventoryLibRootHash["folder_id"] = "00000112-000f-0000-0000-000100bba000"; ArrayList InventoryLibRoot = new ArrayList(); InventoryLibRoot.Add(InventoryLibRootHash); logResponse.InventoryLibRoot = InventoryLibRoot; logResponse.InventoryLibraryOwner = GetLibraryOwner(); logResponse.InventoryRoot = InventoryRoot; logResponse.InventorySkeleton = AgentInventoryArray; logResponse.InventoryLibrary = GetInventoryLibrary(); logResponse.CircuitCode = (Int32)Util.RandomClass.Next(); logResponse.Lastname = userProfile.SurName; logResponse.Firstname = userProfile.FirstName; logResponse.AgentID = agentID; logResponse.SessionID = userProfile.CurrentAgent.SessionID; logResponse.SecureSessionID = userProfile.CurrentAgent.SecureSessionID; logResponse.Message = GetMessage(); logResponse.BuddList = ConvertFriendListItem(m_userManager.GetUserFriendList(agentID)); logResponse.StartLocation = startLocationRequest; try { CustomiseResponse(logResponse, userProfile, startLocationRequest, remoteClient); } catch (Exception ex) { m_log.Info("[LOGIN]: LLSD " + ex.ToString()); return logResponse.CreateDeadRegionResponseLLSD(); } userProfile.LastLogin = userProfile.CurrentAgent.LoginTime; CommitAgent(ref userProfile); // If we reach this point, then the login has successfully logged onto the grid if (StatsManager.UserStats != null) StatsManager.UserStats.AddSuccessfulLogin(); m_log.DebugFormat( "[LOGIN END]: LLSD Authentication of user {0} {1} successful. Sending response to client.", userProfile.FirstName, userProfile.SurName); return logResponse.ToLLSDResponse(); } catch (Exception ex) { m_log.Info("[LOGIN]: LLSD " + ex.ToString()); return logResponse.CreateFailedResponseLLSD(); } } } finally { m_loginMutex.ReleaseMutex(); } } public Hashtable ProcessHTMLLogin(Hashtable keysvals) { // Matches all unspecified characters // Currently specified,; lowercase letters, upper case letters, numbers, underline // period, space, parens, and dash. Regex wfcut = new Regex("[^a-zA-Z0-9_\\.\\$ \\(\\)\\-]"); Hashtable returnactions = new Hashtable(); int statuscode = 200; string firstname = String.Empty; string lastname = String.Empty; string location = String.Empty; string region = String.Empty; string grid = String.Empty; string channel = String.Empty; string version = String.Empty; string lang = String.Empty; string password = String.Empty; string errormessages = String.Empty; // the client requires the HTML form field be named 'username' // however, the data it sends when it loads the first time is 'firstname' // another one of those little nuances. if (keysvals.Contains("firstname")) firstname = wfcut.Replace((string)keysvals["firstname"], String.Empty, 99999); if (keysvals.Contains("username")) firstname = wfcut.Replace((string)keysvals["username"], String.Empty, 99999); if (keysvals.Contains("lastname")) lastname = wfcut.Replace((string)keysvals["lastname"], String.Empty, 99999); if (keysvals.Contains("location")) location = wfcut.Replace((string)keysvals["location"], String.Empty, 99999); if (keysvals.Contains("region")) region = wfcut.Replace((string)keysvals["region"], String.Empty, 99999); if (keysvals.Contains("grid")) grid = wfcut.Replace((string)keysvals["grid"], String.Empty, 99999); if (keysvals.Contains("channel")) channel = wfcut.Replace((string)keysvals["channel"], String.Empty, 99999); if (keysvals.Contains("version")) version = wfcut.Replace((string)keysvals["version"], String.Empty, 99999); if (keysvals.Contains("lang")) lang = wfcut.Replace((string)keysvals["lang"], String.Empty, 99999); if (keysvals.Contains("password")) password = wfcut.Replace((string)keysvals["password"], String.Empty, 99999); // load our login form. string loginform = GetLoginForm(firstname, lastname, location, region, grid, channel, version, lang, password, errormessages); if (keysvals.ContainsKey("show_login_form")) { UserProfileData user = GetTheUser(firstname, lastname); bool goodweblogin = false; if (user != null) goodweblogin = AuthenticateUser(user, password); if (goodweblogin) { UUID webloginkey = UUID.Random(); m_userManager.StoreWebLoginKey(user.ID, webloginkey); //statuscode = 301; // string redirectURL = "about:blank?redirect-http-hack=" + // HttpUtility.UrlEncode("secondlife:///app/login?first_name=" + firstname + "&last_name=" + // lastname + // "&location=" + location + "&grid=Other&web_login_key=" + webloginkey.ToString()); //m_log.Info("[WEB]: R:" + redirectURL); returnactions["int_response_code"] = statuscode; //returnactions["str_redirect_location"] = redirectURL; //returnactions["str_response_string"] = "GoodLogin"; returnactions["str_response_string"] = webloginkey.ToString(); } else { errormessages = "The Username and password supplied did not match our records. Check your caps lock and try again"; loginform = GetLoginForm(firstname, lastname, location, region, grid, channel, version, lang, password, errormessages); returnactions["int_response_code"] = statuscode; returnactions["str_response_string"] = loginform; } } else { returnactions["int_response_code"] = statuscode; returnactions["str_response_string"] = loginform; } return returnactions; } public string GetLoginForm(string firstname, string lastname, string location, string region, string grid, string channel, string version, string lang, string password, string errormessages) { // inject our values in the form at the markers string loginform = String.Empty; string file = Path.Combine(Util.configDir(), "http_loginform.html"); if (!File.Exists(file)) { loginform = GetDefaultLoginForm(); } else { StreamReader sr = File.OpenText(file); loginform = sr.ReadToEnd(); sr.Close(); } loginform = loginform.Replace("[$firstname]", firstname); loginform = loginform.Replace("[$lastname]", lastname); loginform = loginform.Replace("[$location]", location); loginform = loginform.Replace("[$region]", region); loginform = loginform.Replace("[$grid]", grid); loginform = loginform.Replace("[$channel]", channel); loginform = loginform.Replace("[$version]", version); loginform = loginform.Replace("[$lang]", lang); loginform = loginform.Replace("[$password]", password); loginform = loginform.Replace("[$errors]", errormessages); return loginform; } public string GetDefaultLoginForm() { string responseString = ""; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += "OpenSim Login"; responseString += "
"; responseString += "
"; responseString += "
"; responseString += "
[$errors]
"; responseString += "
"; responseString += "First Name:"; responseString += ""; responseString += "
"; responseString += "
"; responseString += "Last Name:"; responseString += ""; responseString += "
"; responseString += "
"; responseString += "Password:"; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += "
"; responseString += "
"; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += ""; responseString += "
"; responseString += ""; responseString += "
"; responseString += "
Connecting...
"; responseString += "
"; responseString += "
[$channel] | [$version]=[$lang]
"; responseString += "
"; responseString += ""; responseString += "
"; responseString += ""; responseString += ""; responseString += ""; return responseString; } /// /// Saves a target agent to the database /// /// The users profile /// Successful? public bool CommitAgent(ref UserProfileData profile) { return m_userManager.CommitAgent(ref profile); } /// /// Checks a user against it's password hash /// /// The users profile /// The supplied password /// Authenticated? public virtual bool AuthenticateUser(UserProfileData profile, string password) { bool passwordSuccess = false; //m_log.InfoFormat("[LOGIN]: Authenticating {0} {1} ({2})", profile.FirstName, profile.SurName, profile.ID); // Web Login method seems to also occasionally send the hashed password itself // we do this to get our hash in a form that the server password code can consume // when the web-login-form submits the password in the clear (supposed to be over SSL!) if (!password.StartsWith("$1$")) password = "$1$" + Util.Md5Hash(password); password = password.Remove(0, 3); //remove $1$ string s = Util.Md5Hash(password + ":" + profile.PasswordSalt); // Testing... //m_log.Info("[LOGIN]: SubHash:" + s + " userprofile:" + profile.passwordHash); //m_log.Info("[LOGIN]: userprofile:" + profile.passwordHash + " SubCT:" + password); passwordSuccess = (profile.PasswordHash.Equals(s.ToString(), StringComparison.InvariantCultureIgnoreCase) || profile.PasswordHash.Equals(password, StringComparison.InvariantCulture)); return passwordSuccess; } public virtual bool AuthenticateUser(UserProfileData profile, UUID webloginkey) { bool passwordSuccess = false; m_log.InfoFormat("[LOGIN]: Authenticating {0} {1} ({2})", profile.FirstName, profile.SurName, profile.ID); // Match web login key unless it's the default weblogin key UUID.Zero passwordSuccess = ((profile.WebLoginKey == webloginkey) && profile.WebLoginKey != UUID.Zero); return passwordSuccess; } /// /// /// /// /// public void CreateAgent(UserProfileData profile, XmlRpcRequest request) { m_userManager.CreateAgent(profile, request); } public void CreateAgent(UserProfileData profile, OSD request) { m_userManager.CreateAgent(profile, request); } /// /// /// /// /// /// public virtual UserProfileData GetTheUser(string firstname, string lastname) { return m_userManager.GetUserProfile(firstname, lastname); } /// /// /// /// public virtual string GetMessage() { return m_welcomeMessage; } private static LoginResponse.BuddyList ConvertFriendListItem(List LFL) { LoginResponse.BuddyList buddylistreturn = new LoginResponse.BuddyList(); foreach (FriendListItem fl in LFL) { LoginResponse.BuddyList.BuddyInfo buddyitem = new LoginResponse.BuddyList.BuddyInfo(fl.Friend); buddyitem.BuddyID = fl.Friend; buddyitem.BuddyRightsHave = (int)fl.FriendListOwnerPerms; buddyitem.BuddyRightsGiven = (int)fl.FriendPerms; buddylistreturn.AddNewBuddy(buddyitem); } return buddylistreturn; } /// /// Converts the inventory library skeleton into the form required by the rpc request. /// /// protected virtual ArrayList GetInventoryLibrary() { Dictionary rootFolders = m_libraryRootFolder.RequestSelfAndDescendentFolders(); ArrayList folderHashes = new ArrayList(); foreach (InventoryFolderBase folder in rootFolders.Values) { Hashtable TempHash = new Hashtable(); TempHash["name"] = folder.Name; TempHash["parent_id"] = folder.ParentID.ToString(); TempHash["version"] = (Int32)folder.Version; TempHash["type_default"] = (Int32)folder.Type; TempHash["folder_id"] = folder.ID.ToString(); folderHashes.Add(TempHash); } return folderHashes; } /// /// /// /// protected virtual ArrayList GetLibraryOwner() { //for now create random inventory library owner Hashtable TempHash = new Hashtable(); TempHash["agent_id"] = "11111111-1111-0000-0000-000100bba000"; ArrayList inventoryLibOwner = new ArrayList(); inventoryLibOwner.Add(TempHash); return inventoryLibOwner; } public class InventoryData { public ArrayList InventoryArray = null; public UUID RootFolderID = UUID.Zero; public InventoryData(ArrayList invList, UUID rootID) { InventoryArray = invList; RootFolderID = rootID; } } protected void SniffLoginKey(Uri uri, Hashtable requestData) { string uri_str = uri.ToString(); string[] parts = uri_str.Split(new char[] { '=' }); if (parts.Length > 1) { string web_login_key = parts[1]; requestData.Add("web_login_key", web_login_key); m_log.InfoFormat("[LOGIN]: Login with web_login_key {0}", web_login_key); } } /// /// Customises the login response and fills in missing values. This method also tells the login region to /// expect a client connection. /// /// The existing response /// The user profile /// The requested start location /// true on success, false if the region was not successfully told to expect a user connection public bool CustomiseResponse(LoginResponse response, UserProfileData theUser, string startLocationRequest, IPEndPoint client) { // add active gestures to login-response AddActiveGestures(response, theUser); // HomeLocation RegionInfo homeInfo = null; // use the homeRegionID if it is stored already. If not, use the regionHandle as before UUID homeRegionId = theUser.HomeRegionID; ulong homeRegionHandle = theUser.HomeRegion; if (homeRegionId != UUID.Zero) { homeInfo = GetRegionInfo(homeRegionId); } else { homeInfo = GetRegionInfo(homeRegionHandle); } if (homeInfo != null) { response.Home = string.Format( "{{'region_handle':[r{0},r{1}], 'position':[r{2},r{3},r{4}], 'look_at':[r{5},r{6},r{7}]}}", (homeInfo.RegionLocX * Constants.RegionSize), (homeInfo.RegionLocY * Constants.RegionSize), theUser.HomeLocation.X, theUser.HomeLocation.Y, theUser.HomeLocation.Z, theUser.HomeLookAt.X, theUser.HomeLookAt.Y, theUser.HomeLookAt.Z); } else { m_log.InfoFormat("not found the region at {0} {1}", theUser.HomeRegionX, theUser.HomeRegionY); // Emergency mode: Home-region isn't available, so we can't request the region info. // Use the stored home regionHandle instead. // NOTE: If the home-region moves, this will be wrong until the users update their user-profile again ulong regionX = homeRegionHandle >> 32; ulong regionY = homeRegionHandle & 0xffffffff; response.Home = string.Format( "{{'region_handle':[r{0},r{1}], 'position':[r{2},r{3},r{4}], 'look_at':[r{5},r{6},r{7}]}}", regionX, regionY, theUser.HomeLocation.X, theUser.HomeLocation.Y, theUser.HomeLocation.Z, theUser.HomeLookAt.X, theUser.HomeLookAt.Y, theUser.HomeLookAt.Z); m_log.InfoFormat("[LOGIN] Home region of user {0} {1} is not available; using computed region position {2} {3}", theUser.FirstName, theUser.SurName, regionX, regionY); } // StartLocation RegionInfo regionInfo = null; if (startLocationRequest == "home") { regionInfo = homeInfo; theUser.CurrentAgent.Position = theUser.HomeLocation; response.LookAt = String.Format("[r{0},r{1},r{2}]", theUser.HomeLookAt.X.ToString(), theUser.HomeLookAt.Y.ToString(), theUser.HomeLookAt.Z.ToString()); } else if (startLocationRequest == "last") { UUID lastRegion = theUser.CurrentAgent.Region; regionInfo = GetRegionInfo(lastRegion); response.LookAt = String.Format("[r{0},r{1},r{2}]", theUser.CurrentAgent.LookAt.X.ToString(), theUser.CurrentAgent.LookAt.Y.ToString(), theUser.CurrentAgent.LookAt.Z.ToString()); } else { Regex reURI = new Regex(@"^uri:(?[^&]+)&(?\d+)&(?\d+)&(?\d+)$"); Match uriMatch = reURI.Match(startLocationRequest); if (uriMatch == null) { m_log.InfoFormat("[LOGIN]: Got Custom Login URL {0}, but can't process it", startLocationRequest); } else { string region = uriMatch.Groups["region"].ToString(); regionInfo = RequestClosestRegion(region); if (regionInfo == null) { m_log.InfoFormat("[LOGIN]: Got Custom Login URL {0}, can't locate region {1}", startLocationRequest, region); } else { theUser.CurrentAgent.Position = new Vector3(float.Parse(uriMatch.Groups["x"].Value, Culture.NumberFormatInfo), float.Parse(uriMatch.Groups["y"].Value, Culture.NumberFormatInfo), float.Parse(uriMatch.Groups["z"].Value, Culture.NumberFormatInfo)); } } response.LookAt = "[r0,r1,r0]"; // can be: last, home, safe, url response.StartLocation = "url"; } if ((regionInfo != null) && (PrepareLoginToRegion(regionInfo, theUser, response, client))) { return true; } // Get the default region handle ulong defaultHandle = Utils.UIntsToLong(m_defaultHomeX * Constants.RegionSize, m_defaultHomeY * Constants.RegionSize); // If we haven't already tried the default region, reset regionInfo if (regionInfo != null && defaultHandle != regionInfo.RegionHandle) regionInfo = null; if (regionInfo == null) { m_log.Error("[LOGIN]: Sending user to default region " + defaultHandle + " instead"); regionInfo = GetRegionInfo(defaultHandle); } if (regionInfo == null) { m_log.ErrorFormat("[LOGIN]: Sending user to any region"); regionInfo = RequestClosestRegion(String.Empty); } theUser.CurrentAgent.Position = new Vector3(128f, 128f, 0f); response.StartLocation = "safe"; return PrepareLoginToRegion(regionInfo, theUser, response, client); } protected abstract RegionInfo RequestClosestRegion(string region); protected abstract RegionInfo GetRegionInfo(ulong homeRegionHandle); protected abstract RegionInfo GetRegionInfo(UUID homeRegionId); /// /// If the user is already logged in, try to notify the region that the user they've got is dead. /// /// public abstract void LogOffUser(UserProfileData theUser, string message); /// /// Prepare a login to the given region. This involves both telling the region to expect a connection /// and appropriately customising the response to the user. /// /// /// /// /// /// true if the region was successfully contacted, false otherwise protected abstract bool PrepareLoginToRegion( RegionInfo regionInfo, UserProfileData user, LoginResponse response, IPEndPoint client); /// /// Add active gestures of the user to the login response. /// /// /// A /// /// /// A /// protected void AddActiveGestures(LoginResponse response, UserProfileData theUser) { List gestures = null; try { if (m_InventoryService != null) gestures = m_InventoryService.GetActiveGestures(theUser.ID); else gestures = m_interInventoryService.GetActiveGestures(theUser.ID); } catch (Exception e) { m_log.Debug("[LOGIN]: Unable to retrieve active gestures from inventory server. Reason: " + e.Message); } //m_log.DebugFormat("[LOGIN]: AddActiveGestures, found {0}", gestures == null ? 0 : gestures.Count); ArrayList list = new ArrayList(); if (gestures != null) { foreach (InventoryItemBase gesture in gestures) { Hashtable item = new Hashtable(); item["item_id"] = gesture.ID.ToString(); item["asset_id"] = gesture.AssetID.ToString(); list.Add(item); } } response.ActiveGestures = list; } /// /// Get the initial login inventory skeleton (in other words, the folder structure) for the given user. /// /// /// /// This will be thrown if there is a problem with the inventory service protected InventoryData GetInventorySkeleton(UUID userID) { List folders = null; if (m_InventoryService != null) { folders = m_InventoryService.GetInventorySkeleton(userID); } else { folders = m_interInventoryService.GetInventorySkeleton(userID); } // If we have user auth but no inventory folders for some reason, create a new set of folders. if (folders == null || folders.Count == 0) { m_log.InfoFormat( "[LOGIN]: A root inventory folder for user {0} was not found. Requesting creation.", userID); // Although the create user function creates a new agent inventory along with a new user profile, some // tools are creating the user profile directly in the database without creating the inventory. At // this time we'll accomodate them by lazily creating the user inventory now if it doesn't already // exist. if (m_interInventoryService != null) { if (!m_interInventoryService.CreateNewUserInventory(userID)) { throw new Exception( String.Format( "The inventory creation request for user {0} did not succeed." + " Please contact your inventory service provider for more information.", userID)); } } else if ((m_InventoryService != null) && !m_InventoryService.CreateUserInventory(userID)) { throw new Exception( String.Format( "The inventory creation request for user {0} did not succeed." + " Please contact your inventory service provider for more information.", userID)); } m_log.InfoFormat("[LOGIN]: A new inventory skeleton was successfully created for user {0}", userID); if (m_InventoryService != null) folders = m_InventoryService.GetInventorySkeleton(userID); else folders = m_interInventoryService.GetInventorySkeleton(userID); if (folders == null || folders.Count == 0) { throw new Exception( String.Format( "A root inventory folder for user {0} could not be retrieved from the inventory service", userID)); } } UUID rootID = UUID.Zero; ArrayList AgentInventoryArray = new ArrayList(); Hashtable TempHash; foreach (InventoryFolderBase InvFolder in folders) { if (InvFolder.ParentID == UUID.Zero) { rootID = InvFolder.ID; } TempHash = new Hashtable(); TempHash["name"] = InvFolder.Name; TempHash["parent_id"] = InvFolder.ParentID.ToString(); TempHash["version"] = (Int32)InvFolder.Version; TempHash["type_default"] = (Int32)InvFolder.Type; TempHash["folder_id"] = InvFolder.ID.ToString(); AgentInventoryArray.Add(TempHash); } return new InventoryData(AgentInventoryArray, rootID); } protected virtual bool AllowLoginWithoutInventory() { return false; } public XmlRpcResponse XmlRPCCheckAuthSession(XmlRpcRequest request, IPEndPoint remoteClient) { XmlRpcResponse response = new XmlRpcResponse(); Hashtable requestData = (Hashtable)request.Params[0]; string authed = "FALSE"; if (requestData.Contains("avatar_uuid") && requestData.Contains("session_id")) { UUID guess_aid; UUID guess_sid; UUID.TryParse((string)requestData["avatar_uuid"], out guess_aid); if (guess_aid == UUID.Zero) { return Util.CreateUnknownUserErrorResponse(); } UUID.TryParse((string)requestData["session_id"], out guess_sid); if (guess_sid == UUID.Zero) { return Util.CreateUnknownUserErrorResponse(); } if (m_userManager.VerifySession(guess_aid, guess_sid)) { authed = "TRUE"; m_log.InfoFormat("[UserManager]: CheckAuthSession TRUE for user {0}", guess_aid); } else { m_log.InfoFormat("[UserManager]: CheckAuthSession FALSE"); return Util.CreateUnknownUserErrorResponse(); } } Hashtable responseData = new Hashtable(); responseData["auth_session"] = authed; response.Value = responseData; return response; } } }