aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Services/LLLoginService
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Services/LLLoginService')
-rw-r--r--OpenSim/Services/LLLoginService/LLLoginResponse.cs971
-rw-r--r--OpenSim/Services/LLLoginService/LLLoginService.cs383
2 files changed, 1354 insertions, 0 deletions
diff --git a/OpenSim/Services/LLLoginService/LLLoginResponse.cs b/OpenSim/Services/LLLoginService/LLLoginResponse.cs
new file mode 100644
index 0000000..4db6a05
--- /dev/null
+++ b/OpenSim/Services/LLLoginService/LLLoginResponse.cs
@@ -0,0 +1,971 @@
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
28using System;
29using System.Collections;
30using System.Collections.Generic;
31using System.Net;
32using System.Reflection;
33
34using OpenSim.Framework;
35using OpenSim.Framework.Capabilities;
36using OpenSim.Services.Interfaces;
37using GridRegion = OpenSim.Services.Interfaces.GridRegion;
38
39using log4net;
40using OpenMetaverse;
41using OpenMetaverse.StructuredData;
42using OSDArray = OpenMetaverse.StructuredData.OSDArray;
43using OSDMap = OpenMetaverse.StructuredData.OSDMap;
44
45namespace OpenSim.Services.LLLoginService
46{
47 public class LLFailedLoginResponse : OpenSim.Services.Interfaces.FailedLoginResponse
48 {
49 string m_key;
50 string m_value;
51 string m_login;
52
53 public static LLFailedLoginResponse UserProblem;
54 public static LLFailedLoginResponse AuthorizationProblem;
55 public static LLFailedLoginResponse GridProblem;
56 public static LLFailedLoginResponse InventoryProblem;
57 public static LLFailedLoginResponse DeadRegionProblem;
58 public static LLFailedLoginResponse LoginBlockedProblem;
59 public static LLFailedLoginResponse AlreadyLoggedInProblem;
60 public static LLFailedLoginResponse InternalError;
61
62 static LLFailedLoginResponse()
63 {
64 UserProblem = new LLFailedLoginResponse("key",
65 "Could not authenticate your avatar. Please check your username and password, and check the grid if problems persist.",
66 "false");
67 AuthorizationProblem = new LLFailedLoginResponse("key",
68 "Error connecting to grid. Unable to authorize your session into the region.",
69 "false");
70 GridProblem = new LLFailedLoginResponse("key",
71 "Error connecting to the desired location. Try connecting to another region.",
72 "false");
73 InventoryProblem = new LLFailedLoginResponse("key",
74 "The inventory service is not responding. Please notify your login region operator.",
75 "false");
76 DeadRegionProblem = new LLFailedLoginResponse("key",
77 "The region you are attempting to log into is not responding. Please select another region and try again.",
78 "false");
79 LoginBlockedProblem = new LLFailedLoginResponse("presence",
80 "Logins are currently restricted. Please try again later.",
81 "false");
82 AlreadyLoggedInProblem = new LLFailedLoginResponse("presence",
83 "You appear to be already logged in. " +
84 "If this is not the case please wait for your session to timeout. " +
85 "If this takes longer than a few minutes please contact the grid owner. " +
86 "Please wait 5 minutes if you are going to connect to a region nearby to the region you were at previously.",
87 "false");
88 InternalError = new LLFailedLoginResponse("Internal Error", "Error generating Login Response", "false");
89 }
90
91 public LLFailedLoginResponse(string key, string value, string login)
92 {
93 m_key = key;
94 m_value = value;
95 m_login = login;
96 }
97
98 public override Hashtable ToHashtable()
99 {
100 Hashtable loginError = new Hashtable();
101 loginError["reason"] = m_key;
102 loginError["message"] = m_value;
103 loginError["login"] = m_login;
104 return loginError;
105 }
106
107 public override OSD ToOSDMap()
108 {
109 OSDMap map = new OSDMap();
110
111 map["reason"] = OSD.FromString(m_key);
112 map["message"] = OSD.FromString(m_value);
113 map["login"] = OSD.FromString(m_login);
114
115 return map;
116 }
117 }
118
119 /// <summary>
120 /// A class to handle LL login response.
121 /// </summary>
122 public class LLLoginResponse : OpenSim.Services.Interfaces.LoginResponse
123 {
124 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
125 private static Hashtable globalTexturesHash;
126 // Global Textures
127 private static string sunTexture = "cce0f112-878f-4586-a2e2-a8f104bba271";
128 private static string cloudTexture = "dc4b9f0b-d008-45c6-96a4-01dd947ac621";
129 private static string moonTexture = "ec4b9f0b-d008-45c6-96a4-01dd947ac621";
130
131 private Hashtable loginFlagsHash;
132 private Hashtable uiConfigHash;
133
134 private ArrayList loginFlags;
135 private ArrayList globalTextures;
136 private ArrayList eventCategories;
137 private ArrayList uiConfig;
138 private ArrayList classifiedCategories;
139 private ArrayList inventoryRoot;
140 private ArrayList initialOutfit;
141 private ArrayList agentInventory;
142 private ArrayList inventoryLibraryOwner;
143 private ArrayList inventoryLibRoot;
144 private ArrayList inventoryLibrary;
145 private ArrayList activeGestures;
146
147 private UserInfo userProfile;
148
149 private UUID agentID;
150 private UUID sessionID;
151 private UUID secureSessionID;
152
153 // Login Flags
154 private string dst;
155 private string stipendSinceLogin;
156 private string gendered;
157 private string everLoggedIn;
158 private string login;
159 private uint simPort;
160 private uint simHttpPort;
161 private string simAddress;
162 private string agentAccess;
163 private string agentAccessMax;
164 private Int32 circuitCode;
165 private uint regionX;
166 private uint regionY;
167
168 // Login
169 private string firstname;
170 private string lastname;
171
172 // Error Flags
173 private string errorReason;
174 private string errorMessage;
175
176 private string welcomeMessage;
177 private string startLocation;
178 private string allowFirstLife;
179 private string home;
180 private string seedCapability;
181 private string lookAt;
182
183 private BuddyList m_buddyList = null;
184
185 static LLLoginResponse()
186 {
187 // This is being set, but it's not used
188 // not sure why.
189 globalTexturesHash = new Hashtable();
190 globalTexturesHash["sun_texture_id"] = sunTexture;
191 globalTexturesHash["cloud_texture_id"] = cloudTexture;
192 globalTexturesHash["moon_texture_id"] = moonTexture;
193 }
194
195 public LLLoginResponse()
196 {
197 loginFlags = new ArrayList();
198 globalTextures = new ArrayList();
199 eventCategories = new ArrayList();
200 uiConfig = new ArrayList();
201 classifiedCategories = new ArrayList();
202
203 uiConfigHash = new Hashtable();
204
205 // defaultXmlRpcResponse = new XmlRpcResponse();
206 userProfile = new UserInfo();
207 inventoryRoot = new ArrayList();
208 initialOutfit = new ArrayList();
209 agentInventory = new ArrayList();
210 inventoryLibrary = new ArrayList();
211 inventoryLibraryOwner = new ArrayList();
212 activeGestures = new ArrayList();
213
214 SetDefaultValues();
215 }
216
217 public LLLoginResponse(UserAccount account, AgentCircuitData aCircuit, PresenceInfo pinfo,
218 GridRegion destination, List<InventoryFolderBase> invSkel, ILibraryService libService,
219 string where, string startlocation, Vector3 position, Vector3 lookAt, string message,
220 GridRegion home, IPEndPoint clientIP)
221 : this()
222 {
223 FillOutInventoryData(invSkel, libService);
224
225 CircuitCode = (int)aCircuit.circuitcode;
226 Lastname = account.LastName;
227 Firstname = account.FirstName;
228 AgentID = account.PrincipalID;
229 SessionID = aCircuit.SessionID;
230 SecureSessionID = aCircuit.SecureSessionID;
231 Message = message;
232 // While we don't have friends...
233 //BuddList = ConvertFriendListItem(m_userManager.GetUserFriendList(agentID));
234 BuddList = new LLLoginResponse.BuddyList();
235 StartLocation = where;
236
237 FillOutHomeData(pinfo, home);
238 LookAt = String.Format("[r{0},r{1},r{2}]", lookAt.X, lookAt.Y, lookAt.Z);
239
240 FillOutRegionData(destination);
241
242 FillOutSeedCap(aCircuit, destination, clientIP);
243
244 }
245
246 private void FillOutInventoryData(List<InventoryFolderBase> invSkel, ILibraryService libService)
247 {
248 InventoryData inventData = null;
249
250 try
251 {
252 inventData = GetInventorySkeleton(invSkel);
253 }
254 catch (Exception e)
255 {
256 m_log.WarnFormat(
257 "[LLLOGIN SERVICE]: Error processing inventory skeleton of agent {0} - {1}",
258 agentID, e);
259
260 // ignore and continue
261 }
262
263 if (inventData != null)
264 {
265 ArrayList AgentInventoryArray = inventData.InventoryArray;
266
267 Hashtable InventoryRootHash = new Hashtable();
268 InventoryRootHash["folder_id"] = inventData.RootFolderID.ToString();
269 InventoryRoot = new ArrayList();
270 InventoryRoot.Add(InventoryRootHash);
271 InventorySkeleton = AgentInventoryArray;
272 }
273
274 // Inventory Library Section
275 if (libService != null && libService.LibraryRootFolder != null)
276 {
277 Hashtable InventoryLibRootHash = new Hashtable();
278 InventoryLibRootHash["folder_id"] = "00000112-000f-0000-0000-000100bba000";
279 InventoryLibRoot = new ArrayList();
280 InventoryLibRoot.Add(InventoryLibRootHash);
281
282 InventoryLibraryOwner = GetLibraryOwner(libService.LibraryRootFolder);
283 InventoryLibrary = GetInventoryLibrary(libService);
284 }
285 }
286
287 private void FillOutHomeData(PresenceInfo pinfo, GridRegion home)
288 {
289 int x = 1000 * (int)Constants.RegionSize, y = 1000 * (int)Constants.RegionSize;
290 if (home != null)
291 {
292 x = home.RegionLocX;
293 y = home.RegionLocY;
294 }
295
296 Home = string.Format(
297 "{{'region_handle':[r{0},r{1}], 'position':[r{2},r{3},r{4}], 'look_at':[r{5},r{6},r{7}]}}",
298 x,
299 y,
300 pinfo.HomePosition.X, pinfo.HomePosition.Y, pinfo.HomePosition.Z,
301 pinfo.HomeLookAt.X, pinfo.HomeLookAt.Y, pinfo.HomeLookAt.Z);
302
303 }
304
305 private void FillOutRegionData(GridRegion destination)
306 {
307 IPEndPoint endPoint = destination.ExternalEndPoint;
308 SimAddress = endPoint.Address.ToString();
309 SimPort = (uint)endPoint.Port;
310 RegionX = (uint)destination.RegionLocX;
311 RegionY = (uint)destination.RegionLocY;
312 }
313
314 private void FillOutSeedCap(AgentCircuitData aCircuit, GridRegion destination, IPEndPoint ipepClient)
315 {
316 string capsSeedPath = String.Empty;
317
318 // Don't use the following! It Fails for logging into any region not on the same port as the http server!
319 // Kept here so it doesn't happen again!
320 // response.SeedCapability = regionInfo.ServerURI + capsSeedPath;
321
322 #region IP Translation for NAT
323 if (ipepClient != null)
324 {
325 capsSeedPath
326 = "http://"
327 + NetworkUtil.GetHostFor(ipepClient.Address, destination.ExternalHostName)
328 + ":"
329 + destination.HttpPort
330 + CapsUtil.GetCapsSeedPath(aCircuit.CapsPath);
331 }
332 else
333 {
334 capsSeedPath
335 = "http://"
336 + destination.ExternalHostName
337 + ":"
338 + destination.HttpPort
339 + CapsUtil.GetCapsSeedPath(aCircuit.CapsPath);
340 }
341 #endregion
342
343 SeedCapability = capsSeedPath;
344 }
345
346 private void SetDefaultValues()
347 {
348 DST = TimeZone.CurrentTimeZone.IsDaylightSavingTime(DateTime.Now) ? "Y" : "N";
349 StipendSinceLogin = "N";
350 Gendered = "Y";
351 EverLoggedIn = "Y";
352 login = "false";
353 firstname = "Test";
354 lastname = "User";
355 agentAccess = "M";
356 agentAccessMax = "A";
357 startLocation = "last";
358 allowFirstLife = "Y";
359
360 ErrorMessage = "You have entered an invalid name/password combination. Check Caps/lock.";
361 ErrorReason = "key";
362 welcomeMessage = "Welcome to OpenSim!";
363 seedCapability = String.Empty;
364 home = "{'region_handle':[r" + (1000*Constants.RegionSize).ToString() + ",r" + (1000*Constants.RegionSize).ToString() + "], 'position':[r" +
365 userProfile.homepos.X.ToString() + ",r" + userProfile.homepos.Y.ToString() + ",r" +
366 userProfile.homepos.Z.ToString() + "], 'look_at':[r" + userProfile.homelookat.X.ToString() + ",r" +
367 userProfile.homelookat.Y.ToString() + ",r" + userProfile.homelookat.Z.ToString() + "]}";
368 lookAt = "[r0.99949799999999999756,r0.03166859999999999814,r0]";
369 RegionX = (uint) 255232;
370 RegionY = (uint) 254976;
371
372 // Classifieds;
373 AddClassifiedCategory((Int32) 1, "Shopping");
374 AddClassifiedCategory((Int32) 2, "Land Rental");
375 AddClassifiedCategory((Int32) 3, "Property Rental");
376 AddClassifiedCategory((Int32) 4, "Special Attraction");
377 AddClassifiedCategory((Int32) 5, "New Products");
378 AddClassifiedCategory((Int32) 6, "Employment");
379 AddClassifiedCategory((Int32) 7, "Wanted");
380 AddClassifiedCategory((Int32) 8, "Service");
381 AddClassifiedCategory((Int32) 9, "Personal");
382
383 SessionID = UUID.Random();
384 SecureSessionID = UUID.Random();
385 AgentID = UUID.Random();
386
387 Hashtable InitialOutfitHash = new Hashtable();
388 InitialOutfitHash["folder_name"] = "Nightclub Female";
389 InitialOutfitHash["gender"] = "female";
390 initialOutfit.Add(InitialOutfitHash);
391 }
392
393
394 public override Hashtable ToHashtable()
395 {
396 try
397 {
398 Hashtable responseData = new Hashtable();
399
400 loginFlagsHash = new Hashtable();
401 loginFlagsHash["daylight_savings"] = DST;
402 loginFlagsHash["stipend_since_login"] = StipendSinceLogin;
403 loginFlagsHash["gendered"] = Gendered;
404 loginFlagsHash["ever_logged_in"] = EverLoggedIn;
405 loginFlags.Add(loginFlagsHash);
406
407 responseData["first_name"] = Firstname;
408 responseData["last_name"] = Lastname;
409 responseData["agent_access"] = agentAccess;
410 responseData["agent_access_max"] = agentAccessMax;
411
412 globalTextures.Add(globalTexturesHash);
413 // this.eventCategories.Add(this.eventCategoriesHash);
414
415 AddToUIConfig("allow_first_life", allowFirstLife);
416 uiConfig.Add(uiConfigHash);
417
418 responseData["sim_port"] = (Int32) SimPort;
419 responseData["sim_ip"] = SimAddress;
420 responseData["http_port"] = (Int32)SimHttpPort;
421
422 responseData["agent_id"] = AgentID.ToString();
423 responseData["session_id"] = SessionID.ToString();
424 responseData["secure_session_id"] = SecureSessionID.ToString();
425 responseData["circuit_code"] = CircuitCode;
426 responseData["seconds_since_epoch"] = (Int32) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
427 responseData["login-flags"] = loginFlags;
428 responseData["global-textures"] = globalTextures;
429 responseData["seed_capability"] = seedCapability;
430
431 responseData["event_categories"] = eventCategories;
432 responseData["event_notifications"] = new ArrayList(); // todo
433 responseData["classified_categories"] = classifiedCategories;
434 responseData["ui-config"] = uiConfig;
435
436 if (agentInventory != null)
437 {
438 responseData["inventory-skeleton"] = agentInventory;
439 responseData["inventory-root"] = inventoryRoot;
440 }
441 responseData["inventory-skel-lib"] = inventoryLibrary;
442 responseData["inventory-lib-root"] = inventoryLibRoot;
443 responseData["gestures"] = activeGestures;
444 responseData["inventory-lib-owner"] = inventoryLibraryOwner;
445 responseData["initial-outfit"] = initialOutfit;
446 responseData["start_location"] = startLocation;
447 responseData["seed_capability"] = seedCapability;
448 responseData["home"] = home;
449 responseData["look_at"] = lookAt;
450 responseData["message"] = welcomeMessage;
451 responseData["region_x"] = (Int32)(RegionX);
452 responseData["region_y"] = (Int32)(RegionY);
453
454 if (m_buddyList != null)
455 {
456 responseData["buddy-list"] = m_buddyList.ToArray();
457 }
458
459 responseData["login"] = "true";
460
461 return responseData;
462 }
463 catch (Exception e)
464 {
465 m_log.Warn("[CLIENT]: LoginResponse: Error creating Hashtable Response: " + e.Message);
466
467 return LLFailedLoginResponse.InternalError.ToHashtable();
468 }
469 }
470
471 public override OSD ToOSDMap()
472 {
473 try
474 {
475 OSDMap map = new OSDMap();
476
477 map["first_name"] = OSD.FromString(Firstname);
478 map["last_name"] = OSD.FromString(Lastname);
479 map["agent_access"] = OSD.FromString(agentAccess);
480 map["agent_access_max"] = OSD.FromString(agentAccessMax);
481
482 map["sim_port"] = OSD.FromInteger(SimPort);
483 map["sim_ip"] = OSD.FromString(SimAddress);
484
485 map["agent_id"] = OSD.FromUUID(AgentID);
486 map["session_id"] = OSD.FromUUID(SessionID);
487 map["secure_session_id"] = OSD.FromUUID(SecureSessionID);
488 map["circuit_code"] = OSD.FromInteger(CircuitCode);
489 map["seconds_since_epoch"] = OSD.FromInteger((int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds);
490
491 #region Login Flags
492
493 OSDMap loginFlagsLLSD = new OSDMap();
494 loginFlagsLLSD["daylight_savings"] = OSD.FromString(DST);
495 loginFlagsLLSD["stipend_since_login"] = OSD.FromString(StipendSinceLogin);
496 loginFlagsLLSD["gendered"] = OSD.FromString(Gendered);
497 loginFlagsLLSD["ever_logged_in"] = OSD.FromString(EverLoggedIn);
498 map["login-flags"] = WrapOSDMap(loginFlagsLLSD);
499
500 #endregion Login Flags
501
502 #region Global Textures
503
504 OSDMap globalTexturesLLSD = new OSDMap();
505 globalTexturesLLSD["sun_texture_id"] = OSD.FromString(SunTexture);
506 globalTexturesLLSD["cloud_texture_id"] = OSD.FromString(CloudTexture);
507 globalTexturesLLSD["moon_texture_id"] = OSD.FromString(MoonTexture);
508
509 map["global-textures"] = WrapOSDMap(globalTexturesLLSD);
510
511 #endregion Global Textures
512
513 map["seed_capability"] = OSD.FromString(seedCapability);
514
515 map["event_categories"] = ArrayListToOSDArray(eventCategories);
516 //map["event_notifications"] = new OSDArray(); // todo
517 map["classified_categories"] = ArrayListToOSDArray(classifiedCategories);
518
519 #region UI Config
520
521 OSDMap uiConfigLLSD = new OSDMap();
522 uiConfigLLSD["allow_first_life"] = OSD.FromString(allowFirstLife);
523 map["ui-config"] = WrapOSDMap(uiConfigLLSD);
524
525 #endregion UI Config
526
527 #region Inventory
528
529 map["inventory-skeleton"] = ArrayListToOSDArray(agentInventory);
530
531 map["inventory-skel-lib"] = ArrayListToOSDArray(inventoryLibrary);
532 map["inventory-root"] = ArrayListToOSDArray(inventoryRoot); ;
533 map["inventory-lib-root"] = ArrayListToOSDArray(inventoryLibRoot);
534 map["inventory-lib-owner"] = ArrayListToOSDArray(inventoryLibraryOwner);
535
536 #endregion Inventory
537
538 map["gestures"] = ArrayListToOSDArray(activeGestures);
539
540 map["initial-outfit"] = ArrayListToOSDArray(initialOutfit);
541 map["start_location"] = OSD.FromString(startLocation);
542
543 map["seed_capability"] = OSD.FromString(seedCapability);
544 map["home"] = OSD.FromString(home);
545 map["look_at"] = OSD.FromString(lookAt);
546 map["message"] = OSD.FromString(welcomeMessage);
547 map["region_x"] = OSD.FromInteger(RegionX);
548 map["region_y"] = OSD.FromInteger(RegionY);
549
550 if (m_buddyList != null)
551 {
552 map["buddy-list"] = ArrayListToOSDArray(m_buddyList.ToArray());
553 }
554
555 map["login"] = OSD.FromString("true");
556
557 return map;
558 }
559 catch (Exception e)
560 {
561 m_log.Warn("[CLIENT]: LoginResponse: Error creating LLSD Response: " + e.Message);
562
563 return LLFailedLoginResponse.InternalError.ToOSDMap();
564 }
565 }
566
567 public OSDArray ArrayListToOSDArray(ArrayList arrlst)
568 {
569 OSDArray llsdBack = new OSDArray();
570 foreach (Hashtable ht in arrlst)
571 {
572 OSDMap mp = new OSDMap();
573 foreach (DictionaryEntry deHt in ht)
574 {
575 mp.Add((string)deHt.Key, OSDString.FromObject(deHt.Value));
576 }
577 llsdBack.Add(mp);
578 }
579 return llsdBack;
580 }
581
582 private static OSDArray WrapOSDMap(OSDMap wrapMe)
583 {
584 OSDArray array = new OSDArray();
585 array.Add(wrapMe);
586 return array;
587 }
588
589 public void SetEventCategories(string category, string value)
590 {
591 // this.eventCategoriesHash[category] = value;
592 //TODO
593 }
594
595 public void AddToUIConfig(string itemName, string item)
596 {
597 uiConfigHash[itemName] = item;
598 }
599
600 public void AddClassifiedCategory(Int32 ID, string categoryName)
601 {
602 Hashtable hash = new Hashtable();
603 hash["category_name"] = categoryName;
604 hash["category_id"] = ID;
605 classifiedCategories.Add(hash);
606 // this.classifiedCategoriesHash.Clear();
607 }
608
609
610 private static LLLoginResponse.BuddyList ConvertFriendListItem(List<FriendListItem> LFL)
611 {
612 LLLoginResponse.BuddyList buddylistreturn = new LLLoginResponse.BuddyList();
613 foreach (FriendListItem fl in LFL)
614 {
615 LLLoginResponse.BuddyList.BuddyInfo buddyitem = new LLLoginResponse.BuddyList.BuddyInfo(fl.Friend);
616 buddyitem.BuddyID = fl.Friend;
617 buddyitem.BuddyRightsHave = (int)fl.FriendListOwnerPerms;
618 buddyitem.BuddyRightsGiven = (int)fl.FriendPerms;
619 buddylistreturn.AddNewBuddy(buddyitem);
620 }
621 return buddylistreturn;
622 }
623
624 private InventoryData GetInventorySkeleton(List<InventoryFolderBase> folders)
625 {
626 UUID rootID = UUID.Zero;
627 ArrayList AgentInventoryArray = new ArrayList();
628 Hashtable TempHash;
629 foreach (InventoryFolderBase InvFolder in folders)
630 {
631 if (InvFolder.ParentID == UUID.Zero)
632 {
633 rootID = InvFolder.ID;
634 }
635 TempHash = new Hashtable();
636 TempHash["name"] = InvFolder.Name;
637 TempHash["parent_id"] = InvFolder.ParentID.ToString();
638 TempHash["version"] = (Int32)InvFolder.Version;
639 TempHash["type_default"] = (Int32)InvFolder.Type;
640 TempHash["folder_id"] = InvFolder.ID.ToString();
641 AgentInventoryArray.Add(TempHash);
642 }
643
644 return new InventoryData(AgentInventoryArray, rootID);
645
646 }
647
648 /// <summary>
649 /// Converts the inventory library skeleton into the form required by the rpc request.
650 /// </summary>
651 /// <returns></returns>
652 protected virtual ArrayList GetInventoryLibrary(ILibraryService library)
653 {
654 Dictionary<UUID, InventoryFolderImpl> rootFolders = library.GetAllFolders();
655 m_log.DebugFormat("[LLOGIN]: Library has {0} folders", rootFolders.Count);
656 //Dictionary<UUID, InventoryFolderImpl> rootFolders = new Dictionary<UUID,InventoryFolderImpl>();
657 ArrayList folderHashes = new ArrayList();
658
659 foreach (InventoryFolderBase folder in rootFolders.Values)
660 {
661 Hashtable TempHash = new Hashtable();
662 TempHash["name"] = folder.Name;
663 TempHash["parent_id"] = folder.ParentID.ToString();
664 TempHash["version"] = (Int32)folder.Version;
665 TempHash["type_default"] = (Int32)folder.Type;
666 TempHash["folder_id"] = folder.ID.ToString();
667 folderHashes.Add(TempHash);
668 }
669
670 return folderHashes;
671 }
672
673 /// <summary>
674 ///
675 /// </summary>
676 /// <returns></returns>
677 protected virtual ArrayList GetLibraryOwner(InventoryFolderImpl libFolder)
678 {
679 //for now create random inventory library owner
680 Hashtable TempHash = new Hashtable();
681 TempHash["agent_id"] = "11111111-1111-0000-0000-000100bba000"; // libFolder.Owner
682 ArrayList inventoryLibOwner = new ArrayList();
683 inventoryLibOwner.Add(TempHash);
684 return inventoryLibOwner;
685 }
686
687 public class InventoryData
688 {
689 public ArrayList InventoryArray = null;
690 public UUID RootFolderID = UUID.Zero;
691
692 public InventoryData(ArrayList invList, UUID rootID)
693 {
694 InventoryArray = invList;
695 RootFolderID = rootID;
696 }
697 }
698
699 #region Properties
700
701 public string Login
702 {
703 get { return login; }
704 set { login = value; }
705 }
706
707 public string DST
708 {
709 get { return dst; }
710 set { dst = value; }
711 }
712
713 public string StipendSinceLogin
714 {
715 get { return stipendSinceLogin; }
716 set { stipendSinceLogin = value; }
717 }
718
719 public string Gendered
720 {
721 get { return gendered; }
722 set { gendered = value; }
723 }
724
725 public string EverLoggedIn
726 {
727 get { return everLoggedIn; }
728 set { everLoggedIn = value; }
729 }
730
731 public uint SimPort
732 {
733 get { return simPort; }
734 set { simPort = value; }
735 }
736
737 public uint SimHttpPort
738 {
739 get { return simHttpPort; }
740 set { simHttpPort = value; }
741 }
742
743 public string SimAddress
744 {
745 get { return simAddress; }
746 set { simAddress = value; }
747 }
748
749 public UUID AgentID
750 {
751 get { return agentID; }
752 set { agentID = value; }
753 }
754
755 public UUID SessionID
756 {
757 get { return sessionID; }
758 set { sessionID = value; }
759 }
760
761 public UUID SecureSessionID
762 {
763 get { return secureSessionID; }
764 set { secureSessionID = value; }
765 }
766
767 public Int32 CircuitCode
768 {
769 get { return circuitCode; }
770 set { circuitCode = value; }
771 }
772
773 public uint RegionX
774 {
775 get { return regionX; }
776 set { regionX = value; }
777 }
778
779 public uint RegionY
780 {
781 get { return regionY; }
782 set { regionY = value; }
783 }
784
785 public string SunTexture
786 {
787 get { return sunTexture; }
788 set { sunTexture = value; }
789 }
790
791 public string CloudTexture
792 {
793 get { return cloudTexture; }
794 set { cloudTexture = value; }
795 }
796
797 public string MoonTexture
798 {
799 get { return moonTexture; }
800 set { moonTexture = value; }
801 }
802
803 public string Firstname
804 {
805 get { return firstname; }
806 set { firstname = value; }
807 }
808
809 public string Lastname
810 {
811 get { return lastname; }
812 set { lastname = value; }
813 }
814
815 public string AgentAccess
816 {
817 get { return agentAccess; }
818 set { agentAccess = value; }
819 }
820
821 public string AgentAccessMax
822 {
823 get { return agentAccessMax; }
824 set { agentAccessMax = value; }
825 }
826
827 public string StartLocation
828 {
829 get { return startLocation; }
830 set { startLocation = value; }
831 }
832
833 public string LookAt
834 {
835 get { return lookAt; }
836 set { lookAt = value; }
837 }
838
839 public string SeedCapability
840 {
841 get { return seedCapability; }
842 set { seedCapability = value; }
843 }
844
845 public string ErrorReason
846 {
847 get { return errorReason; }
848 set { errorReason = value; }
849 }
850
851 public string ErrorMessage
852 {
853 get { return errorMessage; }
854 set { errorMessage = value; }
855 }
856
857 public ArrayList InventoryRoot
858 {
859 get { return inventoryRoot; }
860 set { inventoryRoot = value; }
861 }
862
863 public ArrayList InventorySkeleton
864 {
865 get { return agentInventory; }
866 set { agentInventory = value; }
867 }
868
869 public ArrayList InventoryLibrary
870 {
871 get { return inventoryLibrary; }
872 set { inventoryLibrary = value; }
873 }
874
875 public ArrayList InventoryLibraryOwner
876 {
877 get { return inventoryLibraryOwner; }
878 set { inventoryLibraryOwner = value; }
879 }
880
881 public ArrayList InventoryLibRoot
882 {
883 get { return inventoryLibRoot; }
884 set { inventoryLibRoot = value; }
885 }
886
887 public ArrayList ActiveGestures
888 {
889 get { return activeGestures; }
890 set { activeGestures = value; }
891 }
892
893 public string Home
894 {
895 get { return home; }
896 set { home = value; }
897 }
898
899 public string Message
900 {
901 get { return welcomeMessage; }
902 set { welcomeMessage = value; }
903 }
904
905 public BuddyList BuddList
906 {
907 get { return m_buddyList; }
908 set { m_buddyList = value; }
909 }
910
911 #endregion
912
913 public class UserInfo
914 {
915 public string firstname;
916 public string lastname;
917 public ulong homeregionhandle;
918 public Vector3 homepos;
919 public Vector3 homelookat;
920 }
921
922 public class BuddyList
923 {
924 public List<BuddyInfo> Buddies = new List<BuddyInfo>();
925
926 public void AddNewBuddy(BuddyInfo buddy)
927 {
928 if (!Buddies.Contains(buddy))
929 {
930 Buddies.Add(buddy);
931 }
932 }
933
934 public ArrayList ToArray()
935 {
936 ArrayList buddyArray = new ArrayList();
937 foreach (BuddyInfo buddy in Buddies)
938 {
939 buddyArray.Add(buddy.ToHashTable());
940 }
941 return buddyArray;
942 }
943
944 public class BuddyInfo
945 {
946 public int BuddyRightsHave = 1;
947 public int BuddyRightsGiven = 1;
948 public UUID BuddyID;
949
950 public BuddyInfo(string buddyID)
951 {
952 BuddyID = new UUID(buddyID);
953 }
954
955 public BuddyInfo(UUID buddyID)
956 {
957 BuddyID = buddyID;
958 }
959
960 public Hashtable ToHashTable()
961 {
962 Hashtable hTable = new Hashtable();
963 hTable["buddy_rights_has"] = BuddyRightsHave;
964 hTable["buddy_rights_given"] = BuddyRightsGiven;
965 hTable["buddy_id"] = BuddyID.ToString();
966 return hTable;
967 }
968 }
969 }
970 }
971}
diff --git a/OpenSim/Services/LLLoginService/LLLoginService.cs b/OpenSim/Services/LLLoginService/LLLoginService.cs
new file mode 100644
index 0000000..2ae552f
--- /dev/null
+++ b/OpenSim/Services/LLLoginService/LLLoginService.cs
@@ -0,0 +1,383 @@
1using System;
2using System.Collections.Generic;
3using System.Net;
4using System.Reflection;
5using System.Text.RegularExpressions;
6
7using log4net;
8using Nini.Config;
9using OpenMetaverse;
10
11using OpenSim.Framework;
12using OpenSim.Framework.Capabilities;
13using OpenSim.Server.Base;
14using OpenSim.Services.Interfaces;
15using GridRegion = OpenSim.Services.Interfaces.GridRegion;
16
17namespace OpenSim.Services.LLLoginService
18{
19 public class LLLoginService : ILoginService
20 {
21 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
22
23 private IUserAccountService m_UserAccountService;
24 private IAuthenticationService m_AuthenticationService;
25 private IInventoryService m_InventoryService;
26 private IGridService m_GridService;
27 private IPresenceService m_PresenceService;
28 private ISimulationService m_LocalSimulationService;
29 private ISimulationService m_RemoteSimulationService;
30 private ILibraryService m_LibraryService;
31 private IAvatarService m_AvatarService;
32
33 private string m_DefaultRegionName;
34 private string m_WelcomeMessage;
35 private bool m_RequireInventory;
36
37 public LLLoginService(IConfigSource config, ISimulationService simService, ILibraryService libraryService)
38 {
39 IConfig serverConfig = config.Configs["LoginService"];
40 if (serverConfig == null)
41 throw new Exception(String.Format("No section LoginService in config file"));
42
43 string accountService = serverConfig.GetString("UserAccountService", String.Empty);
44 string authService = serverConfig.GetString("AuthenticationService", String.Empty);
45 string invService = serverConfig.GetString("InventoryService", String.Empty);
46 string gridService = serverConfig.GetString("GridService", String.Empty);
47 string presenceService = serverConfig.GetString("PresenceService", String.Empty);
48 string libService = serverConfig.GetString("LibraryService", String.Empty);
49 string avatarService = serverConfig.GetString("AvatarService", String.Empty);
50 string simulationService = serverConfig.GetString("SimulationService", String.Empty);
51
52 m_DefaultRegionName = serverConfig.GetString("DefaultRegion", String.Empty);
53 m_WelcomeMessage = serverConfig.GetString("WelcomeMessage", "Welcome to OpenSim!");
54 m_RequireInventory = serverConfig.GetBoolean("RequireInventory", true);
55
56 // These are required; the others aren't
57 if (accountService == string.Empty || authService == string.Empty)
58 throw new Exception("LoginService is missing service specifications");
59
60 Object[] args = new Object[] { config };
61 m_UserAccountService = ServerUtils.LoadPlugin<IUserAccountService>(accountService, args);
62 m_AuthenticationService = ServerUtils.LoadPlugin<IAuthenticationService>(authService, args);
63 m_InventoryService = ServerUtils.LoadPlugin<IInventoryService>(invService, args);
64 if (gridService != string.Empty)
65 m_GridService = ServerUtils.LoadPlugin<IGridService>(gridService, args);
66 if (presenceService != string.Empty)
67 m_PresenceService = ServerUtils.LoadPlugin<IPresenceService>(presenceService, args);
68 if (avatarService != string.Empty)
69 m_AvatarService = ServerUtils.LoadPlugin<IAvatarService>(avatarService, args);
70 if (simulationService != string.Empty)
71 m_RemoteSimulationService = ServerUtils.LoadPlugin<ISimulationService>(simulationService, args);
72 //
73 // deal with the services given as argument
74 //
75 m_LocalSimulationService = simService;
76 if (libraryService != null)
77 {
78 m_log.DebugFormat("[LLOGIN SERVICE]: Using LibraryService given as argument");
79 m_LibraryService = libraryService;
80 }
81 else if (libService != string.Empty)
82 {
83 m_log.DebugFormat("[LLOGIN SERVICE]: Using instantiated LibraryService");
84 m_LibraryService = ServerUtils.LoadPlugin<ILibraryService>(libService, args);
85 }
86
87 m_log.DebugFormat("[LLOGIN SERVICE]: Starting...");
88
89 }
90
91 public LLLoginService(IConfigSource config) : this(config, null, null)
92 {
93 }
94
95 public LoginResponse Login(string firstName, string lastName, string passwd, string startLocation, IPEndPoint clientIP)
96 {
97 bool success = false;
98 UUID session = UUID.Random();
99
100 try
101 {
102 // Get the account and check that it exists
103 UserAccount account = m_UserAccountService.GetUserAccount(UUID.Zero, firstName, lastName);
104 if (account == null)
105 {
106 m_log.InfoFormat("[LLOGIN SERVICE]: Login failed, reason: user not found");
107 return LLFailedLoginResponse.UserProblem;
108 }
109
110 // Authenticate this user
111 if (!passwd.StartsWith("$1$"))
112 passwd = "$1$" + Util.Md5Hash(passwd);
113 passwd = passwd.Remove(0, 3); //remove $1$
114 string token = m_AuthenticationService.Authenticate(account.PrincipalID, passwd, 30);
115 UUID secureSession = UUID.Zero;
116 if ((token == string.Empty) || (token != string.Empty && !UUID.TryParse(token, out secureSession)))
117 {
118 m_log.InfoFormat("[LLOGIN SERVICE]: Login failed, reason: authentication failed");
119 return LLFailedLoginResponse.UserProblem;
120 }
121
122 // Get the user's inventory
123 if (m_RequireInventory && m_InventoryService == null)
124 {
125 m_log.WarnFormat("[LLOGIN SERVICE]: Login failed, reason: inventory service not set up");
126 return LLFailedLoginResponse.InventoryProblem;
127 }
128 List<InventoryFolderBase> inventorySkel = m_InventoryService.GetInventorySkeleton(account.PrincipalID);
129 if (m_RequireInventory && ((inventorySkel == null) || (inventorySkel != null && inventorySkel.Count == 0)))
130 {
131 m_log.InfoFormat("[LLOGIN SERVICE]: Login failed, reason: unable to retrieve user inventory");
132 return LLFailedLoginResponse.InventoryProblem;
133 }
134
135 // Login the presence
136 // We may want to check for user already logged in, to
137 // stay compatible with what people expect...
138 PresenceInfo presence = null;
139 GridRegion home = null;
140 if (m_PresenceService != null)
141 {
142 success = m_PresenceService.LoginAgent(account.PrincipalID.ToString(), session, secureSession);
143 if (!success)
144 {
145 m_log.InfoFormat("[LLOGIN SERVICE]: Login failed, reason: could not login presence");
146 return LLFailedLoginResponse.GridProblem;
147 }
148
149 // Get the updated presence info
150 presence = m_PresenceService.GetAgent(session);
151
152 // Get the home region
153 if ((presence.HomeRegionID != UUID.Zero) && m_GridService != null)
154 {
155 home = m_GridService.GetRegionByUUID(account.ScopeID, presence.HomeRegionID);
156 }
157 }
158
159 // Find the destination region/grid
160 string where = string.Empty;
161 Vector3 position = Vector3.Zero;
162 Vector3 lookAt = Vector3.Zero;
163 GridRegion destination = FindDestination(account, presence, session, startLocation, out where, out position, out lookAt);
164 if (destination == null)
165 {
166 m_PresenceService.LogoutAgent(session);
167 m_log.InfoFormat("[LLOGIN SERVICE]: Login failed, reason: destination not found");
168 return LLFailedLoginResponse.GridProblem;
169 }
170
171 // Get the avatar
172 AvatarData avatar = null;
173 if (m_AvatarService != null)
174 {
175 avatar = m_AvatarService.GetAvatar(account.PrincipalID);
176 }
177
178 // Instantiate/get the simulation interface and launch an agent at the destination
179 ISimulationService simConnector = null;
180 string reason = string.Empty;
181 uint circuitCode = 0;
182 AgentCircuitData aCircuit = null;
183 Object[] args = new Object[] { destination };
184 // HG standalones have both a localSimulatonDll and a remoteSimulationDll
185 // non-HG standalones have just a localSimulationDll
186 // independent login servers have just a remoteSimulationDll
187 if (!startLocation.Contains("@") && (m_LocalSimulationService != null))
188 simConnector = m_LocalSimulationService;
189 else if (m_RemoteSimulationService != null)
190 simConnector = m_RemoteSimulationService;
191 if (simConnector != null)
192 {
193 circuitCode = (uint)Util.RandomClass.Next(); ;
194 aCircuit = LaunchAgent(simConnector, destination, account, avatar, session, secureSession, circuitCode, position, out reason);
195 }
196 if (aCircuit == null)
197 {
198 m_PresenceService.LogoutAgent(session);
199 m_log.InfoFormat("[LLOGIN SERVICE]: Login failed, reason: {0}", reason);
200 return LLFailedLoginResponse.AuthorizationProblem;
201 }
202
203 // TODO: Get Friends list...
204
205 // Finally, fill out the response and return it
206 LLLoginResponse response = new LLLoginResponse(account, aCircuit, presence, destination, inventorySkel, m_LibraryService,
207 where, startLocation, position, lookAt, m_WelcomeMessage, home, clientIP);
208
209 return response;
210 }
211 catch (Exception e)
212 {
213 m_log.WarnFormat("[LLOGIN SERVICE]: Exception processing login for {0} {1}: {2}", firstName, lastName, e.StackTrace);
214 if (m_PresenceService != null)
215 m_PresenceService.LogoutAgent(session);
216 return LLFailedLoginResponse.InternalError;
217 }
218 }
219
220 private GridRegion FindDestination(UserAccount account, PresenceInfo pinfo, UUID sessionID, string startLocation, out string where, out Vector3 position, out Vector3 lookAt)
221 {
222 m_log.DebugFormat("[LLOGIN SERVICE]: FindDestination for start location {0}", startLocation);
223
224 where = "home";
225 position = new Vector3(128, 128, 0);
226 lookAt = new Vector3(0, 1, 0);
227 if (startLocation.Equals("home"))
228 {
229 // logging into home region
230 if (m_PresenceService == null || m_GridService == null)
231 return null;
232
233 if (pinfo == null)
234 return null;
235
236 GridRegion region = null;
237
238 if (pinfo.HomeRegionID.Equals(UUID.Zero))
239 {
240 if (m_DefaultRegionName != string.Empty)
241 {
242 region = m_GridService.GetRegionByName(account.ScopeID, m_DefaultRegionName);
243 where = "safe";
244 }
245 else
246 m_log.WarnFormat("[LLOGIN SERVICE]: User {0} {1} does not have a home set and this grid does not have a default location." +
247 "Please specify DefaultRegion in [LoginService]", account.FirstName, account.LastName);
248 }
249 else
250 region = m_GridService.GetRegionByUUID(account.ScopeID, pinfo.HomeRegionID);
251
252 return region;
253 }
254 else if (startLocation.Equals("last"))
255 {
256 // logging into last visited region
257 where = "last";
258 if (m_PresenceService == null || m_GridService == null)
259 return null;
260
261 if (pinfo == null)
262 return null;
263
264 GridRegion region = null;
265
266 if (pinfo.RegionID.Equals(UUID.Zero))
267 {
268 region = m_GridService.GetRegionByName(account.ScopeID, m_DefaultRegionName);
269 where = "safe";
270 }
271 else
272 {
273 region = m_GridService.GetRegionByUUID(account.ScopeID, pinfo.RegionID);
274 position = pinfo.Position;
275 lookAt = pinfo.LookAt;
276 }
277 return region;
278
279 }
280 else
281 {
282 // free uri form
283 // e.g. New Moon&135&46 New Moon@osgrid.org:8002&153&34
284 where = "url";
285 Regex reURI = new Regex(@"^uri:(?<region>[^&]+)&(?<x>\d+)&(?<y>\d+)&(?<z>\d+)$");
286 Match uriMatch = reURI.Match(startLocation);
287 if (uriMatch == null)
288 {
289 m_log.InfoFormat("[LLLOGIN SERVICE]: Got Custom Login URI {0}, but can't process it", startLocation);
290 return null;
291 }
292 else
293 {
294 position = new Vector3(float.Parse(uriMatch.Groups["x"].Value),
295 float.Parse(uriMatch.Groups["y"].Value),
296 float.Parse(uriMatch.Groups["z"].Value));
297
298 string regionName = uriMatch.Groups["region"].ToString();
299 if (regionName != null)
300 {
301 if (!regionName.Contains("@"))
302 {
303 if (m_GridService == null)
304 return null;
305
306 List<GridRegion> regions = m_GridService.GetRegionsByName(account.ScopeID, regionName, 1);
307 if ((regions == null) || (regions != null && regions.Count == 0))
308 {
309 m_log.InfoFormat("[LLLOGIN SERVICE]: Got Custom Login URI {0}, can't locate region {1}", startLocation, regionName);
310 return null;
311 }
312 return regions[0];
313 }
314 else
315 {
316 string[] parts = regionName.Split(new char[] { '@' });
317 if (parts.Length < 2)
318 {
319 m_log.InfoFormat("[LLLOGIN SERVICE]: Got Custom Login URI {0}, can't locate region {1}", startLocation, regionName);
320 return null;
321 }
322 // Valid specification of a remote grid
323 regionName = parts[0];
324 string domainLocator = parts[1];
325 parts = domainLocator.Split(new char[] {':'});
326 string domainName = parts[0];
327 uint port = 0;
328 if (parts.Length > 1)
329 UInt32.TryParse(parts[1], out port);
330 GridRegion region = new GridRegion();
331 region.ExternalHostName = domainName;
332 region.HttpPort = port;
333 region.RegionName = regionName;
334 return region;
335 }
336
337 }
338 else
339 {
340 if (m_PresenceService == null || m_GridService == null)
341 return null;
342
343 return m_GridService.GetRegionByName(account.ScopeID, m_DefaultRegionName);
344
345 }
346 }
347 //response.LookAt = "[r0,r1,r0]";
348 //// can be: last, home, safe, url
349 //response.StartLocation = "url";
350
351 }
352
353 }
354
355 private AgentCircuitData LaunchAgent(ISimulationService simConnector, GridRegion region, UserAccount account,
356 AvatarData avatar, UUID session, UUID secureSession, uint circuit, Vector3 position, out string reason)
357 {
358 reason = string.Empty;
359 AgentCircuitData aCircuit = new AgentCircuitData();
360
361 aCircuit.AgentID = account.PrincipalID;
362 if (avatar != null)
363 aCircuit.Appearance = avatar.ToAvatarAppearance();
364 //aCircuit.BaseFolder = irrelevant
365 aCircuit.CapsPath = CapsUtil.GetRandomCapsObjectPath();
366 aCircuit.child = false; // the first login agent is root
367 aCircuit.ChildrenCapSeeds = new Dictionary<ulong, string>();
368 aCircuit.circuitcode = circuit;
369 aCircuit.firstname = account.FirstName;
370 //aCircuit.InventoryFolder = irrelevant
371 aCircuit.lastname = account.LastName;
372 aCircuit.SecureSessionID = secureSession;
373 aCircuit.SessionID = session;
374 aCircuit.startpos = position;
375
376 if (simConnector.CreateAgent(region, aCircuit, 0, out reason))
377 return aCircuit;
378
379 return null;
380
381 }
382 }
383}