diff options
Moved Linden protocol login handling to modules in OpenSim.Client.Linden. There are two region modules in there LLStandaloneLoginModule (for standalone mode) and LLProxyLoginModule (for grid mode which just handles incoming expect_user and logoff_user messages from the remote login server)
Changed OpenSim.Framework.Communications.Tests.LoginServiceTests to use the LLStandaloneLoginService (from the LLStandaloneLoginModule) rather than LocalLoginService. Really these login tests should most likely be somewhere else as they are testing specific implementations of login services.
Commented out the old LocalLoginService as its no longer used, but want to check there are no problems before it gets deleted.
Diffstat (limited to 'OpenSim/Client/Linden/LLStandaloneLoginService.cs')
-rw-r--r-- | OpenSim/Client/Linden/LLStandaloneLoginService.cs | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/OpenSim/Client/Linden/LLStandaloneLoginService.cs b/OpenSim/Client/Linden/LLStandaloneLoginService.cs new file mode 100644 index 0000000..f772185 --- /dev/null +++ b/OpenSim/Client/Linden/LLStandaloneLoginService.cs | |||
@@ -0,0 +1,433 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Net; | ||
32 | using System.Reflection; | ||
33 | using System.Text.RegularExpressions; | ||
34 | using log4net; | ||
35 | using Nini.Config; | ||
36 | using OpenMetaverse; | ||
37 | using OpenSim.Framework; | ||
38 | using OpenSim.Framework.Communications; | ||
39 | using OpenSim.Framework.Communications.Cache; | ||
40 | using OpenSim.Framework.Communications.Capabilities; | ||
41 | using OpenSim.Framework.Servers; | ||
42 | using OpenSim.Region.Framework.Scenes; | ||
43 | using OpenSim.Region.Framework.Interfaces; | ||
44 | |||
45 | namespace OpenSim.Client.Linden | ||
46 | { | ||
47 | public class LLStandaloneLoginService : LoginService | ||
48 | { | ||
49 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
50 | |||
51 | protected NetworkServersInfo serversInfo; | ||
52 | protected uint defaultHomeX; | ||
53 | protected uint defaultHomeY; | ||
54 | protected bool authUsers = false; | ||
55 | |||
56 | /// <summary> | ||
57 | /// Used by the login service to make requests to the inventory service. | ||
58 | /// </summary> | ||
59 | protected IInterServiceInventoryServices m_interServiceInventoryService; | ||
60 | |||
61 | /// <summary> | ||
62 | /// Used to make requests to the local regions. | ||
63 | /// </summary> | ||
64 | protected ILoginRegionsConnector m_regionsConnector; | ||
65 | |||
66 | |||
67 | public LLStandaloneLoginService( | ||
68 | UserManagerBase userManager, string welcomeMess, | ||
69 | IInterServiceInventoryServices interServiceInventoryService, | ||
70 | NetworkServersInfo serversInfo, | ||
71 | bool authenticate, LibraryRootFolder libraryRootFolder, ILoginRegionsConnector regionsConnector) | ||
72 | : base(userManager, libraryRootFolder, welcomeMess) | ||
73 | { | ||
74 | this.serversInfo = serversInfo; | ||
75 | defaultHomeX = this.serversInfo.DefaultHomeLocX; | ||
76 | defaultHomeY = this.serversInfo.DefaultHomeLocY; | ||
77 | authUsers = authenticate; | ||
78 | |||
79 | m_interServiceInventoryService = interServiceInventoryService; | ||
80 | m_regionsConnector = regionsConnector; | ||
81 | } | ||
82 | |||
83 | public override UserProfileData GetTheUser(string firstname, string lastname) | ||
84 | { | ||
85 | UserProfileData profile = m_userManager.GetUserProfile(firstname, lastname); | ||
86 | if (profile != null) | ||
87 | { | ||
88 | return profile; | ||
89 | } | ||
90 | |||
91 | if (!authUsers) | ||
92 | { | ||
93 | //no current user account so make one | ||
94 | m_log.Info("[LOGIN]: No user account found so creating a new one."); | ||
95 | |||
96 | m_userManager.AddUser(firstname, lastname, "test", "", defaultHomeX, defaultHomeY); | ||
97 | |||
98 | return m_userManager.GetUserProfile(firstname, lastname); | ||
99 | } | ||
100 | |||
101 | return null; | ||
102 | } | ||
103 | |||
104 | public override bool AuthenticateUser(UserProfileData profile, string password) | ||
105 | { | ||
106 | if (!authUsers) | ||
107 | { | ||
108 | //for now we will accept any password in sandbox mode | ||
109 | m_log.Info("[LOGIN]: Authorising user (no actual password check)"); | ||
110 | |||
111 | return true; | ||
112 | } | ||
113 | else | ||
114 | { | ||
115 | m_log.Info( | ||
116 | "[LOGIN]: Authenticating " + profile.FirstName + " " + profile.SurName); | ||
117 | |||
118 | if (!password.StartsWith("$1$")) | ||
119 | password = "$1$" + Util.Md5Hash(password); | ||
120 | |||
121 | password = password.Remove(0, 3); //remove $1$ | ||
122 | |||
123 | string s = Util.Md5Hash(password + ":" + profile.PasswordSalt); | ||
124 | |||
125 | bool loginresult = (profile.PasswordHash.Equals(s.ToString(), StringComparison.InvariantCultureIgnoreCase) | ||
126 | || profile.PasswordHash.Equals(password, StringComparison.InvariantCultureIgnoreCase)); | ||
127 | return loginresult; | ||
128 | } | ||
129 | } | ||
130 | |||
131 | /// <summary> | ||
132 | /// Customises the login response and fills in missing values. | ||
133 | /// </summary> | ||
134 | /// <param name="response">The existing response</param> | ||
135 | /// <param name="theUser">The user profile</param> | ||
136 | /// <param name="startLocationRequest">The requested start location</param> | ||
137 | public override bool CustomiseResponse(LoginResponse response, UserProfileData theUser, string startLocationRequest) | ||
138 | { | ||
139 | // add active gestures to login-response | ||
140 | AddActiveGestures(response, theUser); | ||
141 | |||
142 | // HomeLocation | ||
143 | RegionInfo homeInfo = null; | ||
144 | |||
145 | // use the homeRegionID if it is stored already. If not, use the regionHandle as before | ||
146 | UUID homeRegionId = theUser.HomeRegionID; | ||
147 | ulong homeRegionHandle = theUser.HomeRegion; | ||
148 | if (homeRegionId != UUID.Zero) | ||
149 | { | ||
150 | homeInfo = GetRegionInfo(homeRegionId); | ||
151 | } | ||
152 | else | ||
153 | { | ||
154 | homeInfo = GetRegionInfo(homeRegionHandle); | ||
155 | } | ||
156 | |||
157 | if (homeInfo != null) | ||
158 | { | ||
159 | response.Home = | ||
160 | string.Format( | ||
161 | "{{'region_handle':[r{0},r{1}], 'position':[r{2},r{3},r{4}], 'look_at':[r{5},r{6},r{7}]}}", | ||
162 | (homeInfo.RegionLocX * Constants.RegionSize), | ||
163 | (homeInfo.RegionLocY * Constants.RegionSize), | ||
164 | theUser.HomeLocation.X, theUser.HomeLocation.Y, theUser.HomeLocation.Z, | ||
165 | theUser.HomeLookAt.X, theUser.HomeLookAt.Y, theUser.HomeLookAt.Z); | ||
166 | } | ||
167 | else | ||
168 | { | ||
169 | m_log.InfoFormat("not found the region at {0} {1}", theUser.HomeRegionX, theUser.HomeRegionY); | ||
170 | // Emergency mode: Home-region isn't available, so we can't request the region info. | ||
171 | // Use the stored home regionHandle instead. | ||
172 | // NOTE: If the home-region moves, this will be wrong until the users update their user-profile again | ||
173 | ulong regionX = homeRegionHandle >> 32; | ||
174 | ulong regionY = homeRegionHandle & 0xffffffff; | ||
175 | response.Home = | ||
176 | string.Format( | ||
177 | "{{'region_handle':[r{0},r{1}], 'position':[r{2},r{3},r{4}], 'look_at':[r{5},r{6},r{7}]}}", | ||
178 | regionX, regionY, | ||
179 | theUser.HomeLocation.X, theUser.HomeLocation.Y, theUser.HomeLocation.Z, | ||
180 | theUser.HomeLookAt.X, theUser.HomeLookAt.Y, theUser.HomeLookAt.Z); | ||
181 | |||
182 | m_log.InfoFormat("[LOGIN] Home region of user {0} {1} is not available; using computed region position {2} {3}", | ||
183 | theUser.FirstName, theUser.SurName, | ||
184 | regionX, regionY); | ||
185 | } | ||
186 | |||
187 | // StartLocation | ||
188 | RegionInfo regionInfo = null; | ||
189 | if (startLocationRequest == "home") | ||
190 | { | ||
191 | regionInfo = homeInfo; | ||
192 | theUser.CurrentAgent.Position = theUser.HomeLocation; | ||
193 | response.LookAt = "[r" + theUser.HomeLookAt.X.ToString() + ",r" + theUser.HomeLookAt.Y.ToString() + ",r" + theUser.HomeLookAt.Z.ToString() + "]"; | ||
194 | } | ||
195 | else if (startLocationRequest == "last") | ||
196 | { | ||
197 | UUID lastRegion = theUser.CurrentAgent.Region; | ||
198 | regionInfo = GetRegionInfo(lastRegion); | ||
199 | response.LookAt = "[r" + theUser.CurrentAgent.LookAt.X.ToString() + ",r" + theUser.CurrentAgent.LookAt.Y.ToString() + ",r" + theUser.CurrentAgent.LookAt.Z.ToString() + "]"; | ||
200 | } | ||
201 | else | ||
202 | { | ||
203 | Regex reURI = new Regex(@"^uri:(?<region>[^&]+)&(?<x>\d+)&(?<y>\d+)&(?<z>\d+)$"); | ||
204 | Match uriMatch = reURI.Match(startLocationRequest); | ||
205 | if (uriMatch == null) | ||
206 | { | ||
207 | m_log.InfoFormat("[LOGIN]: Got Custom Login URL {0}, but can't process it", startLocationRequest); | ||
208 | } | ||
209 | else | ||
210 | { | ||
211 | string region = uriMatch.Groups["region"].ToString(); | ||
212 | regionInfo = RequestClosestRegion(region); | ||
213 | if (regionInfo == null) | ||
214 | { | ||
215 | m_log.InfoFormat("[LOGIN]: Got Custom Login URL {0}, can't locate region {1}", startLocationRequest, region); | ||
216 | } | ||
217 | else | ||
218 | { | ||
219 | theUser.CurrentAgent.Position = new Vector3(float.Parse(uriMatch.Groups["x"].Value), | ||
220 | float.Parse(uriMatch.Groups["y"].Value), float.Parse(uriMatch.Groups["z"].Value)); | ||
221 | } | ||
222 | } | ||
223 | response.LookAt = "[r0,r1,r0]"; | ||
224 | // can be: last, home, safe, url | ||
225 | response.StartLocation = "url"; | ||
226 | } | ||
227 | |||
228 | if ((regionInfo != null) && (PrepareLoginToRegion(regionInfo, theUser, response))) | ||
229 | { | ||
230 | return true; | ||
231 | } | ||
232 | |||
233 | // StartLocation not available, send him to a nearby region instead | ||
234 | // regionInfo = m_gridService.RequestClosestRegion(""); | ||
235 | //m_log.InfoFormat("[LOGIN]: StartLocation not available sending to region {0}", regionInfo.regionName); | ||
236 | |||
237 | // Send him to default region instead | ||
238 | ulong defaultHandle = (((ulong)defaultHomeX * Constants.RegionSize) << 32) | | ||
239 | ((ulong)defaultHomeY * Constants.RegionSize); | ||
240 | |||
241 | if ((regionInfo != null) && (defaultHandle == regionInfo.RegionHandle)) | ||
242 | { | ||
243 | m_log.ErrorFormat("[LOGIN]: Not trying the default region since this is the same as the selected region"); | ||
244 | return false; | ||
245 | } | ||
246 | |||
247 | m_log.Error("[LOGIN]: Sending user to default region " + defaultHandle + " instead"); | ||
248 | regionInfo = GetRegionInfo(defaultHandle); | ||
249 | |||
250 | // Customise the response | ||
251 | //response.Home = | ||
252 | // string.Format( | ||
253 | // "{{'region_handle':[r{0},r{1}], 'position':[r{2},r{3},r{4}], 'look_at':[r{5},r{6},r{7}]}}", | ||
254 | // (SimInfo.regionLocX * Constants.RegionSize), | ||
255 | // (SimInfo.regionLocY*Constants.RegionSize), | ||
256 | // theUser.HomeLocation.X, theUser.HomeLocation.Y, theUser.HomeLocation.Z, | ||
257 | // theUser.HomeLookAt.X, theUser.HomeLookAt.Y, theUser.HomeLookAt.Z); | ||
258 | theUser.CurrentAgent.Position = new Vector3(128, 128, 0); | ||
259 | response.StartLocation = "safe"; | ||
260 | |||
261 | return PrepareLoginToRegion(regionInfo, theUser, response); | ||
262 | } | ||
263 | |||
264 | protected RegionInfo RequestClosestRegion(string region) | ||
265 | { | ||
266 | return m_regionsConnector.RequestClosestRegion(region); | ||
267 | } | ||
268 | |||
269 | protected RegionInfo GetRegionInfo(ulong homeRegionHandle) | ||
270 | { | ||
271 | return m_regionsConnector.RequestNeighbourInfo(homeRegionHandle); | ||
272 | } | ||
273 | |||
274 | protected RegionInfo GetRegionInfo(UUID homeRegionId) | ||
275 | { | ||
276 | return m_regionsConnector.RequestNeighbourInfo(homeRegionId); | ||
277 | } | ||
278 | |||
279 | /// <summary> | ||
280 | /// Add active gestures of the user to the login response. | ||
281 | /// </summary> | ||
282 | /// <param name="response"> | ||
283 | /// A <see cref="LoginResponse"/> | ||
284 | /// </param> | ||
285 | /// <param name="theUser"> | ||
286 | /// A <see cref="UserProfileData"/> | ||
287 | /// </param> | ||
288 | private void AddActiveGestures(LoginResponse response, UserProfileData theUser) | ||
289 | { | ||
290 | List<InventoryItemBase> gestures = m_interServiceInventoryService.GetActiveGestures(theUser.ID); | ||
291 | //m_log.DebugFormat("[LOGIN]: AddActiveGestures, found {0}", gestures == null ? 0 : gestures.Count); | ||
292 | ArrayList list = new ArrayList(); | ||
293 | if (gestures != null) | ||
294 | { | ||
295 | foreach (InventoryItemBase gesture in gestures) | ||
296 | { | ||
297 | Hashtable item = new Hashtable(); | ||
298 | item["item_id"] = gesture.ID.ToString(); | ||
299 | item["asset_id"] = gesture.AssetID.ToString(); | ||
300 | list.Add(item); | ||
301 | } | ||
302 | } | ||
303 | response.ActiveGestures = list; | ||
304 | } | ||
305 | |||
306 | /// <summary> | ||
307 | /// Prepare a login to the given region. This involves both telling the region to expect a connection | ||
308 | /// and appropriately customising the response to the user. | ||
309 | /// </summary> | ||
310 | /// <param name="sim"></param> | ||
311 | /// <param name="user"></param> | ||
312 | /// <param name="response"></param> | ||
313 | /// <returns>true if the region was successfully contacted, false otherwise</returns> | ||
314 | protected bool PrepareLoginToRegion(RegionInfo regionInfo, UserProfileData user, LoginResponse response) | ||
315 | { | ||
316 | IPEndPoint endPoint = regionInfo.ExternalEndPoint; | ||
317 | response.SimAddress = endPoint.Address.ToString(); | ||
318 | response.SimPort = (uint)endPoint.Port; | ||
319 | response.RegionX = regionInfo.RegionLocX; | ||
320 | response.RegionY = regionInfo.RegionLocY; | ||
321 | |||
322 | string capsPath = CapsUtil.GetRandomCapsObjectPath(); | ||
323 | string capsSeedPath = CapsUtil.GetCapsSeedPath(capsPath); | ||
324 | |||
325 | // Don't use the following! It Fails for logging into any region not on the same port as the http server! | ||
326 | // Kept here so it doesn't happen again! | ||
327 | // response.SeedCapability = regionInfo.ServerURI + capsSeedPath; | ||
328 | |||
329 | string seedcap = "http://"; | ||
330 | |||
331 | if (serversInfo.HttpUsesSSL) | ||
332 | { | ||
333 | seedcap = "https://" + serversInfo.HttpSSLCN + ":" + serversInfo.httpSSLPort + capsSeedPath; | ||
334 | } | ||
335 | else | ||
336 | { | ||
337 | seedcap = "http://" + regionInfo.ExternalHostName + ":" + serversInfo.HttpListenerPort + capsSeedPath; | ||
338 | } | ||
339 | |||
340 | response.SeedCapability = seedcap; | ||
341 | |||
342 | // Notify the target of an incoming user | ||
343 | m_log.InfoFormat( | ||
344 | "[LOGIN]: Telling {0} @ {1},{2} ({3}) to prepare for client connection", | ||
345 | regionInfo.RegionName, response.RegionX, response.RegionY, regionInfo.ServerURI); | ||
346 | |||
347 | // Update agent with target sim | ||
348 | user.CurrentAgent.Region = regionInfo.RegionID; | ||
349 | user.CurrentAgent.Handle = regionInfo.RegionHandle; | ||
350 | |||
351 | AgentCircuitData agent = new AgentCircuitData(); | ||
352 | agent.AgentID = user.ID; | ||
353 | agent.firstname = user.FirstName; | ||
354 | agent.lastname = user.SurName; | ||
355 | agent.SessionID = user.CurrentAgent.SessionID; | ||
356 | agent.SecureSessionID = user.CurrentAgent.SecureSessionID; | ||
357 | agent.circuitcode = Convert.ToUInt32(response.CircuitCode); | ||
358 | agent.BaseFolder = UUID.Zero; | ||
359 | agent.InventoryFolder = UUID.Zero; | ||
360 | agent.startpos = user.CurrentAgent.Position; | ||
361 | agent.CapsPath = capsPath; | ||
362 | agent.Appearance = m_userManager.GetUserAppearance(user.ID); | ||
363 | if (agent.Appearance == null) | ||
364 | { | ||
365 | m_log.WarnFormat("[INTER]: Appearance not found for {0} {1}. Creating default.", agent.firstname, agent.lastname); | ||
366 | agent.Appearance = new AvatarAppearance(); | ||
367 | } | ||
368 | |||
369 | if (m_regionsConnector.RegionLoginsEnabled) | ||
370 | { | ||
371 | // m_log.Info("[LLStandaloneLoginModule] Informing region about user"); | ||
372 | return m_regionsConnector.NewUserConnection(regionInfo.RegionHandle, agent); | ||
373 | } | ||
374 | |||
375 | return false; | ||
376 | } | ||
377 | |||
378 | // See LoginService | ||
379 | protected override InventoryData GetInventorySkeleton(UUID userID) | ||
380 | { | ||
381 | List<InventoryFolderBase> folders = m_interServiceInventoryService.GetInventorySkeleton(userID); | ||
382 | |||
383 | // If we have user auth but no inventory folders for some reason, create a new set of folders. | ||
384 | if (null == folders || 0 == folders.Count) | ||
385 | { | ||
386 | m_interServiceInventoryService.CreateNewUserInventory(userID); | ||
387 | folders = m_interServiceInventoryService.GetInventorySkeleton(userID); | ||
388 | } | ||
389 | |||
390 | UUID rootID = UUID.Zero; | ||
391 | ArrayList AgentInventoryArray = new ArrayList(); | ||
392 | Hashtable TempHash; | ||
393 | foreach (InventoryFolderBase InvFolder in folders) | ||
394 | { | ||
395 | if (InvFolder.ParentID == UUID.Zero) | ||
396 | { | ||
397 | rootID = InvFolder.ID; | ||
398 | } | ||
399 | TempHash = new Hashtable(); | ||
400 | TempHash["name"] = InvFolder.Name; | ||
401 | TempHash["parent_id"] = InvFolder.ParentID.ToString(); | ||
402 | TempHash["version"] = (Int32)InvFolder.Version; | ||
403 | TempHash["type_default"] = (Int32)InvFolder.Type; | ||
404 | TempHash["folder_id"] = InvFolder.ID.ToString(); | ||
405 | AgentInventoryArray.Add(TempHash); | ||
406 | } | ||
407 | |||
408 | return new InventoryData(AgentInventoryArray, rootID); | ||
409 | } | ||
410 | |||
411 | public override void LogOffUser(UserProfileData theUser, string message) | ||
412 | { | ||
413 | RegionInfo SimInfo; | ||
414 | try | ||
415 | { | ||
416 | SimInfo = this.m_regionsConnector.RequestNeighbourInfo(theUser.CurrentAgent.Handle); | ||
417 | |||
418 | if (SimInfo == null) | ||
419 | { | ||
420 | m_log.Error("[LOCAL LOGIN]: Region user was in isn't currently logged in"); | ||
421 | return; | ||
422 | } | ||
423 | } | ||
424 | catch (Exception) | ||
425 | { | ||
426 | m_log.Error("[LOCAL LOGIN]: Unable to look up region to log user off"); | ||
427 | return; | ||
428 | } | ||
429 | |||
430 | m_regionsConnector.LogOffUserFromGrid(SimInfo.RegionHandle, theUser.ID, theUser.CurrentAgent.SecureSessionID, "Logging you off"); | ||
431 | } | ||
432 | } | ||
433 | } | ||