corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) 2006-2014, openmetaverse.org
3 * All rights reserved.
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 *
8 * - Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 * - Neither the name of the openmetaverse.org nor the names
11 * of its contributors may be used to endorse or promote products derived from
12 * this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26  
27 using System;
28 using System.Collections;
29 using System.Collections.Generic;
30 using System.Threading;
31 using System.IO;
32 using System.Net;
33 using System.Xml;
34 using System.Security.Cryptography.X509Certificates;
35 using Nwc.XmlRpc;
36 using OpenMetaverse.StructuredData;
37 using OpenMetaverse.Http;
38 using OpenMetaverse.Packets;
39  
40 namespace OpenMetaverse
41 {
42 #region Enums
43  
44 /// <summary>
45 ///
46 /// </summary>
47 public enum LoginStatus
48 {
49 /// <summary></summary>
50 Failed = -1,
51 /// <summary></summary>
52 None = 0,
53 /// <summary></summary>
54 ConnectingToLogin,
55 /// <summary></summary>
56 ReadingResponse,
57 /// <summary></summary>
58 ConnectingToSim,
59 /// <summary></summary>
60 Redirecting,
61 /// <summary></summary>
62 Success
63 }
64  
65 /// <summary>
66 /// Status of the last application run.
67 /// Used for error reporting to the grid login service for statistical purposes.
68 /// </summary>
69 public enum LastExecStatus
70 {
71 /// <summary> Application exited normally </summary>
72 Normal = 0,
73 /// <summary> Application froze </summary>
74 Froze,
75 /// <summary> Application detected error and exited abnormally </summary>
76 ForcedCrash,
77 /// <summary> Other crash </summary>
78 OtherCrash,
79 /// <summary> Application froze during logout </summary>
80 LogoutFroze,
81 /// <summary> Application crashed during logout </summary>
82 LogoutCrash
83 }
84  
85 #endregion Enums
86  
87 #region Structs
88  
89 /// <summary>
90 /// Login Request Parameters
91 /// </summary>
92 public class LoginParams
93 {
94 /// <summary>The URL of the Login Server</summary>
95 public string URI;
96 /// <summary>The number of milliseconds to wait before a login is considered
97 /// failed due to timeout</summary>
98 public int Timeout;
99 /// <summary>The request method</summary>
100 /// <remarks>login_to_simulator is currently the only supported method</remarks>
101 public string MethodName;
102 /// <summary>The Agents First name</summary>
103 public string FirstName;
104 /// <summary>The Agents Last name</summary>
105 public string LastName;
106 /// <summary>A md5 hashed password</summary>
107 /// <remarks>plaintext password will be automatically hashed</remarks>
108 public string Password;
109 /// <summary>The agents starting location once logged in</summary>
110 /// <remarks>Either "last", "home", or a string encoded URI
111 /// containing the simulator name and x/y/z coordinates e.g: uri:hooper&amp;128&amp;152&amp;17</remarks>
112 public string Start;
113 /// <summary>A string containing the client software channel information</summary>
114 /// <example>Second Life Release</example>
115 public string Channel;
116 /// <summary>The client software version information</summary>
117 /// <remarks>The official viewer uses: Second Life Release n.n.n.n
118 /// where n is replaced with the current version of the viewer</remarks>
119 public string Version;
120 /// <summary>A string containing the platform information the agent is running on</summary>
121 public string Platform;
122 /// <summary>A string hash of the network cards Mac Address</summary>
123 public string MAC;
124 /// <summary>Unknown or deprecated</summary>
125 public string ViewerDigest;
126 /// <summary>A string hash of the first disk drives ID used to identify this clients uniqueness</summary>
127 public string ID0;
128 /// <summary>A string containing the viewers Software, this is not directly sent to the login server but
129 /// instead is used to generate the Version string</summary>
130 public string UserAgent;
131 /// <summary>A string representing the software creator. This is not directly sent to the login server but
132 /// is used by the library to generate the Version information</summary>
133 public string Author;
134 /// <summary>If true, this agent agrees to the Terms of Service of the grid its connecting to</summary>
135 public bool AgreeToTos;
136 /// <summary>Unknown</summary>
137 public bool ReadCritical;
138 /// <summary>Status of the last application run sent to the grid login server for statistical purposes</summary>
139 public LastExecStatus LastExecEvent = LastExecStatus.Normal;
140  
141 /// <summary>An array of string sent to the login server to enable various options</summary>
142 public string[] Options;
143  
144 /// <summary>A randomly generated ID to distinguish between login attempts. This value is only used
145 /// internally in the library and is never sent over the wire</summary>
146 internal UUID LoginID;
147  
148 /// <summary>
149 /// Default constuctor, initializes sane default values
150 /// </summary>
151 public LoginParams()
152 {
153 List<string> options = new List<string>(16);
154 options.Add("inventory-root");
155 options.Add("inventory-skeleton");
156 options.Add("inventory-lib-root");
157 options.Add("inventory-lib-owner");
158 options.Add("inventory-skel-lib");
159 options.Add("initial-outfit");
160 options.Add("gestures");
161 options.Add("event_categories");
162 options.Add("event_notifications");
163 options.Add("classified_categories");
164 options.Add("buddy-list");
165 options.Add("ui-config");
166 options.Add("tutorial_settings");
167 options.Add("login-flags");
168 options.Add("global-textures");
169 options.Add("adult_compliant");
170  
171 this.Options = options.ToArray();
172 this.MethodName = "login_to_simulator";
173 this.Start = "last";
174 this.Platform = NetworkManager.GetPlatform();
175 this.MAC = NetworkManager.GetMAC();
176 this.ViewerDigest = String.Empty;
177 this.ID0 = NetworkManager.GetMAC();
178 this.AgreeToTos = true;
179 this.ReadCritical = true;
180 this.LastExecEvent = LastExecStatus.Normal;
181 }
182  
183 /// <summary>
184 /// Instantiates new LoginParams object and fills in the values
185 /// </summary>
186 /// <param name="client">Instance of GridClient to read settings from</param>
187 /// <param name="firstName">Login first name</param>
188 /// <param name="lastName">Login last name</param>
189 /// <param name="password">Password</param>
190 /// <param name="channel">Login channnel (application name)</param>
191 /// <param name="version">Client version, should be application name + version number</param>
192 public LoginParams(GridClient client, string firstName, string lastName, string password, string channel, string version)
193 : this()
194 {
195 this.URI = client.Settings.LOGIN_SERVER;
196 this.Timeout = client.Settings.LOGIN_TIMEOUT;
197 this.FirstName = firstName;
198 this.LastName = lastName;
199 this.Password = password;
200 this.Channel = channel;
201 this.Version = version;
202 }
203  
204 /// <summary>
205 /// Instantiates new LoginParams object and fills in the values
206 /// </summary>
207 /// <param name="client">Instance of GridClient to read settings from</param>
208 /// <param name="firstName">Login first name</param>
209 /// <param name="lastName">Login last name</param>
210 /// <param name="password">Password</param>
211 /// <param name="channel">Login channnel (application name)</param>
212 /// <param name="version">Client version, should be application name + version number</param>
213 /// <param name="loginURI">URI of the login server</param>
214 public LoginParams(GridClient client, string firstName, string lastName, string password, string channel, string version, string loginURI)
215 : this(client, firstName, lastName, password, channel, version)
216 {
217 this.URI = loginURI;
218 }
219 }
220  
221 public struct BuddyListEntry
222 {
223 public int buddy_rights_given;
224 public string buddy_id;
225 public int buddy_rights_has;
226 }
227  
228 /// <summary>
229 /// The decoded data returned from the login server after a successful login
230 /// </summary>
231 public struct LoginResponseData
232 {
233 /// <summary>true, false, indeterminate</summary>
234 //[XmlRpcMember("login")]
235 public string Login;
236 public bool Success;
237 public string Reason;
238 /// <summary>Login message of the day</summary>
239 public string Message;
240 public UUID AgentID;
241 public UUID SessionID;
242 public UUID SecureSessionID;
243 public string FirstName;
244 public string LastName;
245 public string StartLocation;
246 /// <summary>M or PG, also agent_region_access and agent_access_max</summary>
247 public string AgentAccess;
248 public Vector3 LookAt;
249 public ulong HomeRegion;
250 public Vector3 HomePosition;
251 public Vector3 HomeLookAt;
252 public int CircuitCode;
253 public int RegionX;
254 public int RegionY;
255 public int SimPort;
256 public IPAddress SimIP;
257 public string SeedCapability;
258 public BuddyListEntry[] BuddyList;
259 public int SecondsSinceEpoch;
260 public string UDPBlacklist;
261  
262 #region Inventory
263  
264 public UUID InventoryRoot;
265 public UUID LibraryRoot;
266 public InventoryFolder[] InventorySkeleton;
267 public InventoryFolder[] LibrarySkeleton;
268 public UUID LibraryOwner;
269  
270 #endregion
271  
272 #region Redirection
273  
274 public string NextMethod;
275 public string NextUrl;
276 public string[] NextOptions;
277 public int NextDuration;
278  
279 #endregion
280  
281 // These aren't currently being utilized by the library
282 public string AgentAccessMax;
283 public string AgentRegionAccess;
284 public int AOTransition;
285 public string InventoryHost;
286 public int MaxAgentGroups;
287 public string OpenIDUrl;
288 public string AgentAppearanceServiceURL;
289 public uint COFVersion;
290 public string InitialOutfit;
291 public bool FirstLogin;
292  
293 /// <summary>
294 /// Parse LLSD Login Reply Data
295 /// </summary>
296 /// <param name="reply">An <seealso cref="OSDMap"/>
297 /// contaning the login response data</param>
298 /// <remarks>XML-RPC logins do not require this as XML-RPC.NET
299 /// automatically populates the struct properly using attributes</remarks>
300 public void Parse(OSDMap reply)
301 {
302 try
303 {
304 AgentID = ParseUUID("agent_id", reply);
305 SessionID = ParseUUID("session_id", reply);
306 SecureSessionID = ParseUUID("secure_session_id", reply);
307 FirstName = ParseString("first_name", reply).Trim('"');
308 LastName = ParseString("last_name", reply).Trim('"');
309 StartLocation = ParseString("start_location", reply);
310 AgentAccess = ParseString("agent_access", reply);
311 LookAt = ParseVector3("look_at", reply);
312 Reason = ParseString("reason", reply);
313 Message = ParseString("message", reply);
314  
315 Login = reply["login"].AsString();
316 Success = reply["login"].AsBoolean();
317 }
318 catch (OSDException e)
319 {
320 Logger.Log("Login server returned (some) invalid data: " + e.Message, Helpers.LogLevel.Warning);
321 }
322  
323 // Home
324 OSDMap home = null;
325 OSD osdHome = OSDParser.DeserializeLLSDNotation(reply["home"].AsString());
326  
327 if (osdHome.Type == OSDType.Map)
328 {
329 home = (OSDMap)osdHome;
330  
331 OSD homeRegion;
332 if (home.TryGetValue("region_handle", out homeRegion) && homeRegion.Type == OSDType.Array)
333 {
334 OSDArray homeArray = (OSDArray)homeRegion;
335 if (homeArray.Count == 2)
336 HomeRegion = Utils.UIntsToLong((uint)homeArray[0].AsInteger(), (uint)homeArray[1].AsInteger());
337 else
338 HomeRegion = 0;
339 }
340  
341 HomePosition = ParseVector3("position", home);
342 HomeLookAt = ParseVector3("look_at", home);
343 }
344 else
345 {
346 HomeRegion = 0;
347 HomePosition = Vector3.Zero;
348 HomeLookAt = Vector3.Zero;
349 }
350  
351 CircuitCode = (int)ParseUInt("circuit_code", reply);
352 RegionX = (int)ParseUInt("region_x", reply);
353 RegionY = (int)ParseUInt("region_y", reply);
354 SimPort = (short)ParseUInt("sim_port", reply);
355 string simIP = ParseString("sim_ip", reply);
356 IPAddress.TryParse(simIP, out SimIP);
357 SeedCapability = ParseString("seed_capability", reply);
358  
359 // Buddy list
360 OSD buddyLLSD;
361 if (reply.TryGetValue("buddy-list", out buddyLLSD) && buddyLLSD.Type == OSDType.Array)
362 {
363 List<BuddyListEntry> buddys = new List<BuddyListEntry>();
364 OSDArray buddyArray = (OSDArray)buddyLLSD;
365 for (int i = 0; i < buddyArray.Count; i++)
366 {
367 if (buddyArray[i].Type == OSDType.Map)
368 {
369 BuddyListEntry bud = new BuddyListEntry();
370 OSDMap buddy = (OSDMap)buddyArray[i];
371  
372 bud.buddy_id = buddy["buddy_id"].AsString();
373 bud.buddy_rights_given = (int)ParseUInt("buddy_rights_given", buddy);
374 bud.buddy_rights_has = (int)ParseUInt("buddy_rights_has", buddy);
375  
376 buddys.Add(bud);
377 }
378 BuddyList = buddys.ToArray();
379 }
380 }
381  
382 SecondsSinceEpoch = (int)ParseUInt("seconds_since_epoch", reply);
383  
384 InventoryRoot = ParseMappedUUID("inventory-root", "folder_id", reply);
385 InventorySkeleton = ParseInventorySkeleton("inventory-skeleton", reply);
386  
387 LibraryOwner = ParseMappedUUID("inventory-lib-owner", "agent_id", reply);
388 LibraryRoot = ParseMappedUUID("inventory-lib-root", "folder_id", reply);
389 LibrarySkeleton = ParseInventorySkeleton("inventory-skel-lib", reply);
390 }
391  
392 public void Parse(Hashtable reply)
393 {
394 try
395 {
396 AgentID = ParseUUID("agent_id", reply);
397 SessionID = ParseUUID("session_id", reply);
398 SecureSessionID = ParseUUID("secure_session_id", reply);
399 FirstName = ParseString("first_name", reply).Trim('"');
400 LastName = ParseString("last_name", reply).Trim('"');
401 // "first_login" for brand new accounts
402 StartLocation = ParseString("start_location", reply);
403 AgentAccess = ParseString("agent_access", reply);
404 LookAt = ParseVector3("look_at", reply);
405 Reason = ParseString("reason", reply);
406 Message = ParseString("message", reply);
407  
408 if (reply.ContainsKey("login"))
409 {
410 Login = (string)reply["login"];
411 Success = Login == "true";
412  
413 // Parse redirect options
414 if (Login == "indeterminate")
415 {
416 NextUrl = ParseString("next_url", reply);
417 NextDuration = (int)ParseUInt("next_duration", reply);
418 NextMethod = ParseString("next_method", reply);
419 NextOptions = (string[])((ArrayList)reply["next_options"]).ToArray(typeof(string));
420 }
421 }
422 }
423 catch (Exception e)
424 {
425 Logger.Log("Login server returned (some) invalid data: " + e.Message, Helpers.LogLevel.Warning);
426 }
427 if (!Success)
428 return;
429  
430 // Home
431 OSDMap home = null;
432 if (reply.ContainsKey("home"))
433 {
434 OSD osdHome = OSDParser.DeserializeLLSDNotation(reply["home"].ToString());
435  
436 if (osdHome.Type == OSDType.Map)
437 {
438 home = (OSDMap)osdHome;
439  
440 OSD homeRegion;
441 if (home.TryGetValue("region_handle", out homeRegion) && homeRegion.Type == OSDType.Array)
442 {
443 OSDArray homeArray = (OSDArray)homeRegion;
444 if (homeArray.Count == 2)
445 HomeRegion = Utils.UIntsToLong((uint)homeArray[0].AsInteger(),
446 (uint)homeArray[1].AsInteger());
447 else
448 HomeRegion = 0;
449 }
450  
451 HomePosition = ParseVector3("position", home);
452 HomeLookAt = ParseVector3("look_at", home);
453 }
454 }
455 else
456 {
457 HomeRegion = 0;
458 HomePosition = Vector3.Zero;
459 HomeLookAt = Vector3.Zero;
460 }
461  
462 CircuitCode = (int)ParseUInt("circuit_code", reply);
463 RegionX = (int)ParseUInt("region_x", reply);
464 RegionY = (int)ParseUInt("region_y", reply);
465 SimPort = (short)ParseUInt("sim_port", reply);
466 string simIP = ParseString("sim_ip", reply);
467 IPAddress.TryParse(simIP, out SimIP);
468 SeedCapability = ParseString("seed_capability", reply);
469  
470 // Buddy list
471 if (reply.ContainsKey("buddy-list") && reply["buddy-list"] is ArrayList)
472 {
473 List<BuddyListEntry> buddys = new List<BuddyListEntry>();
474  
475 ArrayList buddyArray = (ArrayList)reply["buddy-list"];
476 for (int i = 0; i < buddyArray.Count; i++)
477 {
478 if (buddyArray[i] is Hashtable)
479 {
480 BuddyListEntry bud = new BuddyListEntry();
481 Hashtable buddy = (Hashtable)buddyArray[i];
482  
483 bud.buddy_id = ParseString("buddy_id", buddy);
484 bud.buddy_rights_given = (int)ParseUInt("buddy_rights_given", buddy);
485 bud.buddy_rights_has = (int)ParseUInt("buddy_rights_has", buddy);
486  
487 buddys.Add(bud);
488 }
489 }
490  
491 BuddyList = buddys.ToArray();
492 }
493  
494 SecondsSinceEpoch = (int)ParseUInt("seconds_since_epoch", reply);
495  
496 InventoryRoot = ParseMappedUUID("inventory-root", "folder_id", reply);
497 InventorySkeleton = ParseInventorySkeleton("inventory-skeleton", reply);
498  
499 LibraryOwner = ParseMappedUUID("inventory-lib-owner", "agent_id", reply);
500 LibraryRoot = ParseMappedUUID("inventory-lib-root", "folder_id", reply);
501 LibrarySkeleton = ParseInventorySkeleton("inventory-skel-lib", reply);
502  
503 // UDP Blacklist
504 if (reply.ContainsKey("udp_blacklist"))
505 {
506 UDPBlacklist = ParseString("udp_blacklist", reply);
507 }
508  
509 if (reply.ContainsKey("max-agent-groups"))
510 {
511 MaxAgentGroups = (int)ParseUInt("max-agent-groups", reply);
512 }
513 else
514 {
515 MaxAgentGroups = -1;
516 }
517  
518 if (reply.ContainsKey("openid_url"))
519 {
520 OpenIDUrl = ParseString("openid_url", reply);
521 }
522  
523 if (reply.ContainsKey("agent_appearance_service"))
524 {
525 AgentAppearanceServiceURL = ParseString("agent_appearance_service", reply);
526 }
527  
528 COFVersion = 0;
529 if (reply.ContainsKey("cof_version"))
530 {
531 COFVersion = ParseUInt("cof_version", reply);
532 }
533  
534 InitialOutfit = string.Empty;
535 if (reply.ContainsKey("initial-outfit") && reply["initial-outfit"] is ArrayList)
536 {
537 ArrayList array = (ArrayList)reply["initial-outfit"];
538 for (int i = 0; i < array.Count; i++)
539 {
540 if (array[i] is Hashtable)
541 {
542 Hashtable map = (Hashtable)array[i];
543 InitialOutfit = ParseString("folder_name", map);
544 }
545 }
546 }
547  
548 FirstLogin = false;
549 if (reply.ContainsKey("login-flags") && reply["login-flags"] is ArrayList)
550 {
551 ArrayList array = (ArrayList)reply["login-flags"];
552 for (int i = 0; i < array.Count; i++)
553 {
554 if (array[i] is Hashtable)
555 {
556 Hashtable map = (Hashtable)array[i];
557 FirstLogin = ParseString("ever_logged_in", map) == "N";
558 }
559 }
560 }
561  
562  
563 }
564  
565 #region Parsing Helpers
566  
567 public static uint ParseUInt(string key, OSDMap reply)
568 {
569 OSD osd;
570 if (reply.TryGetValue(key, out osd))
571 return osd.AsUInteger();
572 else
573 return 0;
574 }
575  
576 public static uint ParseUInt(string key, Hashtable reply)
577 {
578 if (reply.ContainsKey(key))
579 {
580 object value = reply[key];
581 if (value is int)
582 return (uint)(int)value;
583 }
584  
585 return 0;
586 }
587  
588 public static UUID ParseUUID(string key, OSDMap reply)
589 {
590 OSD osd;
591 if (reply.TryGetValue(key, out osd))
592 return osd.AsUUID();
593 else
594 return UUID.Zero;
595 }
596  
597 public static UUID ParseUUID(string key, Hashtable reply)
598 {
599 if (reply.ContainsKey(key))
600 {
601 UUID value;
602 if (UUID.TryParse((string)reply[key], out value))
603 return value;
604 }
605  
606 return UUID.Zero;
607 }
608  
609 public static string ParseString(string key, OSDMap reply)
610 {
611 OSD osd;
612 if (reply.TryGetValue(key, out osd))
613 return osd.AsString();
614 else
615 return String.Empty;
616 }
617  
618 public static string ParseString(string key, Hashtable reply)
619 {
620 if (reply.ContainsKey(key))
621 return String.Format("{0}", reply[key]);
622  
623 return String.Empty;
624 }
625  
626 public static Vector3 ParseVector3(string key, OSDMap reply)
627 {
628 OSD osd;
629 if (reply.TryGetValue(key, out osd))
630 {
631 if (osd.Type == OSDType.Array)
632 {
633 return ((OSDArray)osd).AsVector3();
634 }
635 else if (osd.Type == OSDType.String)
636 {
637 OSDArray array = (OSDArray)OSDParser.DeserializeLLSDNotation(osd.AsString());
638 return array.AsVector3();
639 }
640 }
641  
642 return Vector3.Zero;
643 }
644  
645 public static Vector3 ParseVector3(string key, Hashtable reply)
646 {
647 if (reply.ContainsKey(key))
648 {
649 object value = reply[key];
650  
651 if (value is IList)
652 {
653 IList list = (IList)value;
654 if (list.Count == 3)
655 {
656 float x, y, z;
657 Single.TryParse((string)list[0], out x);
658 Single.TryParse((string)list[1], out y);
659 Single.TryParse((string)list[2], out z);
660  
661 return new Vector3(x, y, z);
662 }
663 }
664 else if (value is string)
665 {
666 OSDArray array = (OSDArray)OSDParser.DeserializeLLSDNotation((string)value);
667 return array.AsVector3();
668 }
669 }
670  
671 return Vector3.Zero;
672 }
673  
674 public static UUID ParseMappedUUID(string key, string key2, OSDMap reply)
675 {
676 OSD folderOSD;
677 if (reply.TryGetValue(key, out folderOSD) && folderOSD.Type == OSDType.Array)
678 {
679 OSDArray array = (OSDArray)folderOSD;
680 if (array.Count == 1 && array[0].Type == OSDType.Map)
681 {
682 OSDMap map = (OSDMap)array[0];
683 OSD folder;
684 if (map.TryGetValue(key2, out folder))
685 return folder.AsUUID();
686 }
687 }
688  
689 return UUID.Zero;
690 }
691  
692 public static UUID ParseMappedUUID(string key, string key2, Hashtable reply)
693 {
694 if (reply.ContainsKey(key) && reply[key] is ArrayList)
695 {
696 ArrayList array = (ArrayList)reply[key];
697 if (array.Count == 1 && array[0] is Hashtable)
698 {
699 Hashtable map = (Hashtable)array[0];
700 return ParseUUID(key2, map);
701 }
702 }
703  
704 return UUID.Zero;
705 }
706  
707 public static InventoryFolder[] ParseInventoryFolders(string key, UUID owner, OSDMap reply)
708 {
709 List<InventoryFolder> folders = new List<InventoryFolder>();
710  
711 OSD skeleton;
712 if (reply.TryGetValue(key, out skeleton) && skeleton.Type == OSDType.Array)
713 {
714 OSDArray array = (OSDArray)skeleton;
715  
716 for (int i = 0; i < array.Count; i++)
717 {
718 if (array[i].Type == OSDType.Map)
719 {
720 OSDMap map = (OSDMap)array[i];
721 InventoryFolder folder = new InventoryFolder(map["folder_id"].AsUUID());
722 folder.PreferredType = (AssetType)map["type_default"].AsInteger();
723 folder.Version = map["version"].AsInteger();
724 folder.OwnerID = owner;
725 folder.ParentUUID = map["parent_id"].AsUUID();
726 folder.Name = map["name"].AsString();
727  
728 folders.Add(folder);
729 }
730 }
731 }
732  
733 return folders.ToArray();
734 }
735  
736 public InventoryFolder[] ParseInventorySkeleton(string key, OSDMap reply)
737 {
738 List<InventoryFolder> folders = new List<InventoryFolder>();
739  
740 OSD skeleton;
741 if (reply.TryGetValue(key, out skeleton) && skeleton.Type == OSDType.Array)
742 {
743 OSDArray array = (OSDArray)skeleton;
744 for (int i = 0; i < array.Count; i++)
745 {
746 if (array[i].Type == OSDType.Map)
747 {
748 OSDMap map = (OSDMap)array[i];
749 InventoryFolder folder = new InventoryFolder(map["folder_id"].AsUUID());
750 folder.Name = map["name"].AsString();
751 folder.ParentUUID = map["parent_id"].AsUUID();
752 folder.PreferredType = (AssetType)map["type_default"].AsInteger();
753 folder.Version = map["version"].AsInteger();
754 folders.Add(folder);
755 }
756 }
757 }
758 return folders.ToArray();
759 }
760  
761 public InventoryFolder[] ParseInventorySkeleton(string key, Hashtable reply)
762 {
763 UUID ownerID;
764 if (key.Equals("inventory-skel-lib"))
765 ownerID = LibraryOwner;
766 else
767 ownerID = AgentID;
768  
769 List<InventoryFolder> folders = new List<InventoryFolder>();
770  
771 if (reply.ContainsKey(key) && reply[key] is ArrayList)
772 {
773 ArrayList array = (ArrayList)reply[key];
774 for (int i = 0; i < array.Count; i++)
775 {
776 if (array[i] is Hashtable)
777 {
778 Hashtable map = (Hashtable)array[i];
779 InventoryFolder folder = new InventoryFolder(ParseUUID("folder_id", map));
780 folder.Name = ParseString("name", map);
781 folder.ParentUUID = ParseUUID("parent_id", map);
782 folder.PreferredType = (AssetType)ParseUInt("type_default", map);
783 folder.Version = (int)ParseUInt("version", map);
784 folder.OwnerID = ownerID;
785  
786 folders.Add(folder);
787 }
788 }
789 }
790  
791 return folders.ToArray();
792 }
793  
794 #endregion Parsing Helpers
795 }
796  
797 #endregion Structs
798  
799 /// <summary>
800 /// Login Routines
801 /// </summary>
802 public partial class NetworkManager
803 {
804 #region Delegates
805  
806  
807 //////LoginProgress
808 //// LoginProgress
809 /// <summary>The event subscribers, null of no subscribers</summary>
810 private EventHandler<LoginProgressEventArgs> m_LoginProgress;
811  
812 ///<summary>Raises the LoginProgress Event</summary>
813 /// <param name="e">A LoginProgressEventArgs object containing
814 /// the data sent from the simulator</param>
815 protected virtual void OnLoginProgress(LoginProgressEventArgs e)
816 {
817 EventHandler<LoginProgressEventArgs> handler = m_LoginProgress;
818 if (handler != null)
819 handler(this, e);
820 }
821  
822 /// <summary>Thread sync lock object</summary>
823 private readonly object m_LoginProgressLock = new object();
824  
825 /// <summary>Raised when the simulator sends us data containing
826 /// ...</summary>
827 public event EventHandler<LoginProgressEventArgs> LoginProgress
828 {
829 add { lock (m_LoginProgressLock) { m_LoginProgress += value; } }
830 remove { lock (m_LoginProgressLock) { m_LoginProgress -= value; } }
831 }
832  
833 ///// <summary>The event subscribers, null of no subscribers</summary>
834 //private EventHandler<LoggedInEventArgs> m_LoggedIn;
835  
836 /////<summary>Raises the LoggedIn Event</summary>
837 ///// <param name="e">A LoggedInEventArgs object containing
838 ///// the data sent from the simulator</param>
839 //protected virtual void OnLoggedIn(LoggedInEventArgs e)
840 //{
841 // EventHandler<LoggedInEventArgs> handler = m_LoggedIn;
842 // if (handler != null)
843 // handler(this, e);
844 //}
845  
846 ///// <summary>Thread sync lock object</summary>
847 //private readonly object m_LoggedInLock = new object();
848  
849 ///// <summary>Raised when the simulator sends us data containing
850 ///// ...</summary>
851 //public event EventHandler<LoggedInEventArgs> LoggedIn
852 //{
853 // add { lock (m_LoggedInLock) { m_LoggedIn += value; } }
854 // remove { lock (m_LoggedInLock) { m_LoggedIn -= value; } }
855 //}
856  
857 /// <summary>
858 ///
859 /// </summary>
860 /// <param name="loginSuccess"></param>
861 /// <param name="redirect"></param>
862 /// <param name="replyData"></param>
863 /// <param name="message"></param>
864 /// <param name="reason"></param>
865 public delegate void LoginResponseCallback(bool loginSuccess, bool redirect, string message, string reason, LoginResponseData replyData);
866  
867 #endregion Delegates
868  
869 #region Events
870  
871 /// <summary>Called when a reply is received from the login server, the
872 /// login sequence will block until this event returns</summary>
873 private event LoginResponseCallback OnLoginResponse;
874  
875 #endregion Events
876  
877 #region Public Members
878 /// <summary>Seed CAPS URL returned from the login server</summary>
879 public string LoginSeedCapability = String.Empty;
880 /// <summary>Current state of logging in</summary>
881 public LoginStatus LoginStatusCode { get { return InternalStatusCode; } }
882 /// <summary>Upon login failure, contains a short string key for the
883 /// type of login error that occurred</summary>
884 public string LoginErrorKey { get { return InternalErrorKey; } }
885 /// <summary>The raw XML-RPC reply from the login server, exactly as it
886 /// was received (minus the HTTP header)</summary>
887 public string RawLoginReply { get { return InternalRawLoginReply; } }
888 /// <summary>During login this contains a descriptive version of
889 /// LoginStatusCode. After a successful login this will contain the
890 /// message of the day, and after a failed login a descriptive error
891 /// message will be returned</summary>
892 public string LoginMessage { get { return InternalLoginMessage; } }
893 /// <summary>Maximum number of groups an agent can belong to, -1 for unlimited</summary>
894 public int MaxAgentGroups = -1;
895 /// <summary>Server side baking service URL</summary>
896 public string AgentAppearanceServiceURL;
897 /// <summary>Parsed login response data</summary>
898 public LoginResponseData LoginResponseData;
899 #endregion
900  
901 #region Private Members
902 private LoginParams CurrentContext = null;
903 private AutoResetEvent LoginEvent = new AutoResetEvent(false);
904 private LoginStatus InternalStatusCode = LoginStatus.None;
905 private string InternalErrorKey = String.Empty;
906 private string InternalLoginMessage = String.Empty;
907 private string InternalRawLoginReply = String.Empty;
908 private Dictionary<LoginResponseCallback, string[]> CallbackOptions = new Dictionary<LoginResponseCallback, string[]>();
909  
910 /// <summary>A list of packets obtained during the login process which
911 /// networkmanager will log but not process</summary>
912 private readonly List<string> UDPBlacklist = new List<string>();
913 #endregion
914  
915 #region Public Methods
916  
917 /// <summary>
918 /// Generate sane default values for a login request
919 /// </summary>
920 /// <param name="firstName">Account first name</param>
921 /// <param name="lastName">Account last name</param>
922 /// <param name="password">Account password</param>
923 /// <param name="channel">Client application name (channel)</param>
924 /// <param name="version">Client application name + version</param>
925 /// <returns>A populated <seealso cref="LoginParams"/> struct containing
926 /// sane defaults</returns>
927 public LoginParams DefaultLoginParams(string firstName, string lastName, string password,
928 string channel, string version)
929 {
930 return new LoginParams(Client, firstName, lastName, password, channel, version);
931 }
932  
933 /// <summary>
934 /// Simplified login that takes the most common and required fields
935 /// </summary>
936 /// <param name="firstName">Account first name</param>
937 /// <param name="lastName">Account last name</param>
938 /// <param name="password">Account password</param>
939 /// <param name="channel">Client application name (channel)</param>
940 /// <param name="version">Client application name + version</param>
941 /// <returns>Whether the login was successful or not. On failure the
942 /// LoginErrorKey string will contain the error code and LoginMessage
943 /// will contain a description of the error</returns>
944 public bool Login(string firstName, string lastName, string password, string channel, string version)
945 {
946 return Login(firstName, lastName, password, channel, "last", version);
947 }
948  
949 /// <summary>
950 /// Simplified login that takes the most common fields along with a
951 /// starting location URI, and can accept an MD5 string instead of a
952 /// plaintext password
953 /// </summary>
954 /// <param name="firstName">Account first name</param>
955 /// <param name="lastName">Account last name</param>
956 /// <param name="password">Account password or MD5 hash of the password
957 /// such as $1$1682a1e45e9f957dcdf0bb56eb43319c</param>
958 /// <param name="channel">Client application name (channel)</param>
959 /// <param name="start">Starting location URI that can be built with
960 /// StartLocation()</param>
961 /// <param name="version">Client application name + version</param>
962 /// <returns>Whether the login was successful or not. On failure the
963 /// LoginErrorKey string will contain the error code and LoginMessage
964 /// will contain a description of the error</returns>
965 public bool Login(string firstName, string lastName, string password, string channel, string start,
966 string version)
967 {
968 LoginParams loginParams = DefaultLoginParams(firstName, lastName, password, channel, version);
969 loginParams.Start = start;
970  
971 return Login(loginParams);
972 }
973  
974 /// <summary>
975 /// Login that takes a struct of all the values that will be passed to
976 /// the login server
977 /// </summary>
978 /// <param name="loginParams">The values that will be passed to the login
979 /// server, all fields must be set even if they are String.Empty</param>
980 /// <returns>Whether the login was successful or not. On failure the
981 /// LoginErrorKey string will contain the error code and LoginMessage
982 /// will contain a description of the error</returns>
983 public bool Login(LoginParams loginParams)
984 {
985 BeginLogin(loginParams);
986  
987 LoginEvent.WaitOne(loginParams.Timeout, false);
988  
989 if (CurrentContext != null)
990 {
991 CurrentContext = null; // Will force any pending callbacks to bail out early
992 InternalStatusCode = LoginStatus.Failed;
993 InternalLoginMessage = "Timed out";
994 return false;
995 }
996  
997 return (InternalStatusCode == LoginStatus.Success);
998 }
999  
1000 public void BeginLogin(LoginParams loginParams)
1001 {
1002 // FIXME: Now that we're using CAPS we could cancel the current login and start a new one
1003 if (CurrentContext != null)
1004 throw new Exception("Login already in progress");
1005  
1006 LoginEvent.Reset();
1007 CurrentContext = loginParams;
1008  
1009 BeginLogin();
1010 }
1011  
1012 public void RegisterLoginResponseCallback(LoginResponseCallback callback)
1013 {
1014 RegisterLoginResponseCallback(callback, null);
1015 }
1016  
1017  
1018 public void RegisterLoginResponseCallback(LoginResponseCallback callback, string[] options)
1019 {
1020 CallbackOptions.Add(callback, options);
1021 OnLoginResponse += callback;
1022 }
1023  
1024 public void UnregisterLoginResponseCallback(LoginResponseCallback callback)
1025 {
1026 CallbackOptions.Remove(callback);
1027 OnLoginResponse -= callback;
1028 }
1029  
1030 /// <summary>
1031 /// Build a start location URI for passing to the Login function
1032 /// </summary>
1033 /// <param name="sim">Name of the simulator to start in</param>
1034 /// <param name="x">X coordinate to start at</param>
1035 /// <param name="y">Y coordinate to start at</param>
1036 /// <param name="z">Z coordinate to start at</param>
1037 /// <returns>String with a URI that can be used to login to a specified
1038 /// location</returns>
1039 public static string StartLocation(string sim, int x, int y, int z)
1040 {
1041 return String.Format("uri:{0}&{1}&{2}&{3}", sim, x, y, z);
1042 }
1043 public void AbortLogin()
1044 {
1045 LoginParams loginParams = CurrentContext;
1046 CurrentContext = null; // Will force any pending callbacks to bail out early
1047 // FIXME: Now that we're using CAPS we could cancel the current login and start a new one
1048 if (loginParams == null)
1049 {
1050 Logger.DebugLog("No Login was in progress: " + CurrentContext, Client);
1051 }
1052 else
1053 {
1054 InternalStatusCode = LoginStatus.Failed;
1055 InternalLoginMessage = "Aborted";
1056 }
1057 UpdateLoginStatus(LoginStatus.Failed, "Abort Requested");
1058 }
1059  
1060 #endregion
1061  
1062 #region Private Methods
1063  
1064 private void BeginLogin()
1065 {
1066 LoginParams loginParams = CurrentContext;
1067 // Generate a random ID to identify this login attempt
1068 loginParams.LoginID = UUID.Random();
1069 CurrentContext = loginParams;
1070  
1071 #region Sanity Check loginParams
1072  
1073 if (loginParams.Options == null)
1074 loginParams.Options = new List<string>().ToArray();
1075  
1076 if (loginParams.Password == null)
1077 loginParams.Password = String.Empty;
1078  
1079 // Convert the password to MD5 if it isn't already
1080 if (loginParams.Password.Length != 35 && !loginParams.Password.StartsWith("$1$"))
1081 loginParams.Password = Utils.MD5(loginParams.Password);
1082  
1083 if (loginParams.ViewerDigest == null)
1084 loginParams.ViewerDigest = String.Empty;
1085  
1086 if (loginParams.Version == null)
1087 loginParams.Version = String.Empty;
1088  
1089 if (loginParams.UserAgent == null)
1090 loginParams.UserAgent = String.Empty;
1091  
1092 if (loginParams.Platform == null)
1093 loginParams.Platform = String.Empty;
1094  
1095 if (loginParams.MAC == null)
1096 loginParams.MAC = String.Empty;
1097  
1098 if (string.IsNullOrEmpty(loginParams.Channel))
1099 {
1100 Logger.Log("Viewer channel not set. This is a TOS violation on some grids.", Helpers.LogLevel.Warning);
1101 loginParams.Channel = "libopenmetaverse generic client";
1102 }
1103  
1104 if (loginParams.Author == null)
1105 loginParams.Author = String.Empty;
1106  
1107 #endregion
1108  
1109 // TODO: Allow a user callback to be defined for handling the cert
1110 ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy();
1111 // Even though this will compile on Mono 2.4, it throws a runtime exception
1112 //ServicePointManager.ServerCertificateValidationCallback = TrustAllCertificatePolicy.TrustAllCertificateHandler;
1113  
1114 if (Client.Settings.USE_LLSD_LOGIN)
1115 {
1116 #region LLSD Based Login
1117  
1118 // Create the CAPS login structure
1119 OSDMap loginLLSD = new OSDMap();
1120 loginLLSD["first"] = OSD.FromString(loginParams.FirstName);
1121 loginLLSD["last"] = OSD.FromString(loginParams.LastName);
1122 loginLLSD["passwd"] = OSD.FromString(loginParams.Password);
1123 loginLLSD["start"] = OSD.FromString(loginParams.Start);
1124 loginLLSD["channel"] = OSD.FromString(loginParams.Channel);
1125 loginLLSD["version"] = OSD.FromString(loginParams.Version);
1126 loginLLSD["platform"] = OSD.FromString(loginParams.Platform);
1127 loginLLSD["mac"] = OSD.FromString(loginParams.MAC);
1128 loginLLSD["agree_to_tos"] = OSD.FromBoolean(loginParams.AgreeToTos);
1129 loginLLSD["read_critical"] = OSD.FromBoolean(loginParams.ReadCritical);
1130 loginLLSD["viewer_digest"] = OSD.FromString(loginParams.ViewerDigest);
1131 loginLLSD["id0"] = OSD.FromString(loginParams.ID0);
1132 loginLLSD["last_exec_event"] = OSD.FromInteger((int)loginParams.LastExecEvent);
1133  
1134 // Create the options LLSD array
1135 OSDArray optionsOSD = new OSDArray();
1136 for (int i = 0; i < loginParams.Options.Length; i++)
1137 optionsOSD.Add(OSD.FromString(loginParams.Options[i]));
1138  
1139 foreach (string[] callbackOpts in CallbackOptions.Values)
1140 {
1141 if (callbackOpts != null)
1142 {
1143 for (int i = 0; i < callbackOpts.Length; i++)
1144 {
1145 if (!optionsOSD.Contains(callbackOpts[i]))
1146 optionsOSD.Add(callbackOpts[i]);
1147 }
1148 }
1149 }
1150 loginLLSD["options"] = optionsOSD;
1151  
1152 // Make the CAPS POST for login
1153 Uri loginUri;
1154 try
1155 {
1156 loginUri = new Uri(loginParams.URI);
1157 }
1158 catch (Exception ex)
1159 {
1160 Logger.Log(String.Format("Failed to parse login URI {0}, {1}", loginParams.URI, ex.Message),
1161 Helpers.LogLevel.Error, Client);
1162 return;
1163 }
1164  
1165 CapsClient loginRequest = new CapsClient(loginUri);
1166 loginRequest.OnComplete += new CapsClient.CompleteCallback(LoginReplyLLSDHandler);
1167 loginRequest.UserData = CurrentContext;
1168 UpdateLoginStatus(LoginStatus.ConnectingToLogin, String.Format("Logging in as {0} {1}...", loginParams.FirstName, loginParams.LastName));
1169 loginRequest.BeginGetResponse(loginLLSD, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT);
1170  
1171 #endregion
1172 }
1173 else
1174 {
1175 #region XML-RPC Based Login Code
1176  
1177 // Create the Hashtable for XmlRpcCs
1178 Hashtable loginXmlRpc = new Hashtable();
1179 loginXmlRpc["first"] = loginParams.FirstName;
1180 loginXmlRpc["last"] = loginParams.LastName;
1181 loginXmlRpc["passwd"] = loginParams.Password;
1182 loginXmlRpc["start"] = loginParams.Start;
1183 loginXmlRpc["channel"] = loginParams.Channel;
1184 loginXmlRpc["version"] = loginParams.Version;
1185 loginXmlRpc["platform"] = loginParams.Platform;
1186 loginXmlRpc["mac"] = loginParams.MAC;
1187 if (loginParams.AgreeToTos)
1188 loginXmlRpc["agree_to_tos"] = "true";
1189 if (loginParams.ReadCritical)
1190 loginXmlRpc["read_critical"] = "true";
1191 loginXmlRpc["id0"] = loginParams.ID0;
1192 loginXmlRpc["last_exec_event"] = (int)loginParams.LastExecEvent;
1193  
1194 // Create the options array
1195 ArrayList options = new ArrayList();
1196 for (int i = 0; i < loginParams.Options.Length; i++)
1197 options.Add(loginParams.Options[i]);
1198  
1199 foreach (string[] callbackOpts in CallbackOptions.Values)
1200 {
1201 if (callbackOpts != null)
1202 {
1203 for (int i = 0; i < callbackOpts.Length; i++)
1204 {
1205 if (!options.Contains(callbackOpts[i]))
1206 options.Add(callbackOpts[i]);
1207 }
1208 }
1209 }
1210 loginXmlRpc["options"] = options;
1211  
1212 try
1213 {
1214 ArrayList loginArray = new ArrayList(1);
1215 loginArray.Add(loginXmlRpc);
1216 XmlRpcRequest request = new XmlRpcRequest(CurrentContext.MethodName, loginArray);
1217 var cc = CurrentContext;
1218 // Start the request
1219 Thread requestThread = new Thread(
1220 delegate()
1221 {
1222 try
1223 {
1224 LoginReplyXmlRpcHandler(
1225 request.Send(cc.URI, cc.Timeout),
1226 loginParams);
1227 }
1228 catch (Exception e)
1229 {
1230 UpdateLoginStatus(LoginStatus.Failed, "Error opening the login server connection: " + e.Message);
1231 }
1232 });
1233 requestThread.Name = "XML-RPC Login";
1234 requestThread.Start();
1235 }
1236 catch (Exception e)
1237 {
1238 UpdateLoginStatus(LoginStatus.Failed, "Error opening the login server connection: " + e);
1239 }
1240  
1241 #endregion
1242 }
1243 }
1244  
1245 private void UpdateLoginStatus(LoginStatus status, string message)
1246 {
1247 InternalStatusCode = status;
1248 InternalLoginMessage = message;
1249  
1250 Logger.DebugLog("Login status: " + status.ToString() + ": " + message, Client);
1251  
1252 // If we reached a login resolution trigger the event
1253 if (status == LoginStatus.Success || status == LoginStatus.Failed)
1254 {
1255 CurrentContext = null;
1256 LoginEvent.Set();
1257 }
1258  
1259 // Fire the login status callback
1260 if (m_LoginProgress != null)
1261 {
1262 OnLoginProgress(new LoginProgressEventArgs(status, message, InternalErrorKey));
1263 }
1264 }
1265  
1266  
1267 /// <summary>
1268 /// LoginParams and the initial login XmlRpcRequest were made on a remote machine.
1269 /// This method now initializes libomv with the results.
1270 /// </summary>
1271 public void RemoteLoginHandler(LoginResponseData response, LoginParams newContext)
1272 {
1273 CurrentContext = newContext;
1274 LoginReplyXmlRpcHandler(response, newContext);
1275 }
1276  
1277  
1278 /// <summary>
1279 /// Handles response from XML-RPC login replies
1280 /// </summary>
1281 private void LoginReplyXmlRpcHandler(XmlRpcResponse response, LoginParams context)
1282 {
1283 LoginResponseData reply = new LoginResponseData();
1284 // Fetch the login response
1285 if (response == null || !(response.Value is Hashtable))
1286 {
1287 UpdateLoginStatus(LoginStatus.Failed, "Invalid or missing login response from the server");
1288 Logger.Log("Invalid or missing login response from the server", Helpers.LogLevel.Warning);
1289 return;
1290 }
1291  
1292 try
1293 {
1294 reply.Parse((Hashtable)response.Value);
1295 if (context.LoginID != CurrentContext.LoginID)
1296 {
1297 Logger.Log("Login response does not match login request. Only one login can be attempted at a time",
1298 Helpers.LogLevel.Error);
1299 return;
1300 }
1301 }
1302 catch (Exception e)
1303 {
1304 UpdateLoginStatus(LoginStatus.Failed, "Error retrieving the login response from the server: " + e.Message);
1305 Logger.Log("Login response failure: " + e.Message + " " + e.StackTrace, Helpers.LogLevel.Warning);
1306 return;
1307 }
1308 LoginReplyXmlRpcHandler(reply, context);
1309 }
1310  
1311  
1312 /// <summary>
1313 /// Handles response from XML-RPC login replies with already parsed LoginResponseData
1314 /// </summary>
1315 private void LoginReplyXmlRpcHandler(LoginResponseData reply, LoginParams context)
1316 {
1317 LoginResponseData = reply;
1318 ushort simPort = 0;
1319 uint regionX = 0;
1320 uint regionY = 0;
1321 string reason = reply.Reason;
1322 string message = reply.Message;
1323  
1324 if (reply.Login == "true")
1325 {
1326 // Remove the quotes around our first name.
1327 if (reply.FirstName[0] == '"')
1328 reply.FirstName = reply.FirstName.Remove(0, 1);
1329 if (reply.FirstName[reply.FirstName.Length - 1] == '"')
1330 reply.FirstName = reply.FirstName.Remove(reply.FirstName.Length - 1);
1331  
1332 #region Critical Information
1333  
1334 try
1335 {
1336 // Networking
1337 Client.Network.CircuitCode = (uint)reply.CircuitCode;
1338 regionX = (uint)reply.RegionX;
1339 regionY = (uint)reply.RegionY;
1340 simPort = (ushort)reply.SimPort;
1341 LoginSeedCapability = reply.SeedCapability;
1342 }
1343 catch (Exception)
1344 {
1345 UpdateLoginStatus(LoginStatus.Failed, "Login server failed to return critical information");
1346 return;
1347 }
1348  
1349 #endregion Critical Information
1350  
1351 /* Add any blacklisted UDP packets to the blacklist
1352 * for exclusion from packet processing */
1353 if (reply.UDPBlacklist != null)
1354 UDPBlacklist.AddRange(reply.UDPBlacklist.Split(','));
1355  
1356 // Misc:
1357 MaxAgentGroups = reply.MaxAgentGroups;
1358 AgentAppearanceServiceURL = reply.AgentAppearanceServiceURL;
1359  
1360 //uint timestamp = (uint)reply.seconds_since_epoch;
1361 //DateTime time = Helpers.UnixTimeToDateTime(timestamp); // TODO: Do something with this?
1362  
1363 // Unhandled:
1364 // reply.gestures
1365 // reply.event_categories
1366 // reply.classified_categories
1367 // reply.event_notifications
1368 // reply.ui_config
1369 // reply.login_flags
1370 // reply.global_textures
1371 // reply.inventory_lib_root
1372 // reply.inventory_lib_owner
1373 // reply.inventory_skeleton
1374 // reply.inventory_skel_lib
1375 // reply.initial_outfit
1376 }
1377  
1378 bool redirect = (reply.Login == "indeterminate");
1379  
1380 try
1381 {
1382 if (OnLoginResponse != null)
1383 {
1384 try { OnLoginResponse(reply.Success, redirect, message, reason, reply); }
1385 catch (Exception ex) { Logger.Log(ex.ToString(), Helpers.LogLevel.Error); }
1386 }
1387 }
1388 catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, ex); }
1389  
1390 // Make the next network jump, if needed
1391 if (redirect)
1392 {
1393 UpdateLoginStatus(LoginStatus.Redirecting, "Redirecting login...");
1394 LoginParams loginParams = CurrentContext;
1395 loginParams.URI = reply.NextUrl;
1396 loginParams.MethodName = reply.NextMethod;
1397 loginParams.Options = reply.NextOptions;
1398  
1399 // Sleep for some amount of time while the servers work
1400 int seconds = reply.NextDuration;
1401 Logger.Log("Sleeping for " + seconds + " seconds during a login redirect",
1402 Helpers.LogLevel.Info);
1403 Thread.Sleep(seconds * 1000);
1404  
1405 CurrentContext = loginParams;
1406 BeginLogin();
1407 }
1408 else if (reply.Success)
1409 {
1410 UpdateLoginStatus(LoginStatus.ConnectingToSim, "Connecting to simulator...");
1411  
1412 ulong handle = Utils.UIntsToLong(regionX, regionY);
1413  
1414 // Connect to the sim given in the login reply
1415 if (Connect(reply.SimIP, simPort, handle, true, LoginSeedCapability) != null)
1416 {
1417 // Request the economy data right after login
1418 SendPacket(new EconomyDataRequestPacket());
1419  
1420 // Update the login message with the MOTD returned from the server
1421 UpdateLoginStatus(LoginStatus.Success, message);
1422 }
1423 else
1424 {
1425 UpdateLoginStatus(LoginStatus.Failed, "Unable to connect to simulator");
1426 }
1427 }
1428 else
1429 {
1430 // Make sure a usable error key is set
1431  
1432 if (!String.IsNullOrEmpty(reason))
1433 InternalErrorKey = reason;
1434 else
1435 InternalErrorKey = "unknown";
1436  
1437 UpdateLoginStatus(LoginStatus.Failed, message);
1438 }
1439 }
1440  
1441 /// <summary>
1442 /// Handle response from LLSD login replies
1443 /// </summary>
1444 /// <param name="client"></param>
1445 /// <param name="result"></param>
1446 /// <param name="error"></param>
1447 private void LoginReplyLLSDHandler(CapsClient client, OSD result, Exception error)
1448 {
1449 if (error == null)
1450 {
1451 if (result != null && result.Type == OSDType.Map)
1452 {
1453 OSDMap map = (OSDMap)result;
1454 OSD osd;
1455  
1456 LoginResponseData data = new LoginResponseData();
1457 data.Parse(map);
1458  
1459 if (map.TryGetValue("login", out osd))
1460 {
1461 bool loginSuccess = osd.AsBoolean();
1462 bool redirect = (osd.AsString() == "indeterminate");
1463  
1464 if (redirect)
1465 {
1466 // Login redirected
1467  
1468 // Make the next login URL jump
1469 UpdateLoginStatus(LoginStatus.Redirecting, data.Message);
1470  
1471 LoginParams loginParams = CurrentContext;
1472 loginParams.URI = LoginResponseData.ParseString("next_url", map);
1473 //CurrentContext.Params.MethodName = LoginResponseData.ParseString("next_method", map);
1474  
1475 // Sleep for some amount of time while the servers work
1476 int seconds = (int)LoginResponseData.ParseUInt("next_duration", map);
1477 Logger.Log("Sleeping for " + seconds + " seconds during a login redirect",
1478 Helpers.LogLevel.Info);
1479 Thread.Sleep(seconds * 1000);
1480  
1481 // Ignore next_options for now
1482 CurrentContext = loginParams;
1483  
1484 BeginLogin();
1485 }
1486 else if (loginSuccess)
1487 {
1488 // Login succeeded
1489  
1490 // Fire the login callback
1491 if (OnLoginResponse != null)
1492 {
1493 try { OnLoginResponse(loginSuccess, redirect, data.Message, data.Reason, data); }
1494 catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client, ex); }
1495 }
1496  
1497 // These parameters are stored in NetworkManager, so instead of registering
1498 // another callback for them we just set the values here
1499 CircuitCode = (uint)data.CircuitCode;
1500 LoginSeedCapability = data.SeedCapability;
1501  
1502 UpdateLoginStatus(LoginStatus.ConnectingToSim, "Connecting to simulator...");
1503  
1504 ulong handle = Utils.UIntsToLong((uint)data.RegionX, (uint)data.RegionY);
1505  
1506 if (data.SimIP != null && data.SimPort != 0)
1507 {
1508 // Connect to the sim given in the login reply
1509 if (Connect(data.SimIP, (ushort)data.SimPort, handle, true, LoginSeedCapability) != null)
1510 {
1511 // Request the economy data right after login
1512 SendPacket(new EconomyDataRequestPacket());
1513  
1514 // Update the login message with the MOTD returned from the server
1515 UpdateLoginStatus(LoginStatus.Success, data.Message);
1516 }
1517 else
1518 {
1519 UpdateLoginStatus(LoginStatus.Failed,
1520 "Unable to establish a UDP connection to the simulator");
1521 }
1522 }
1523 else
1524 {
1525 UpdateLoginStatus(LoginStatus.Failed,
1526 "Login server did not return a simulator address");
1527 }
1528 }
1529 else
1530 {
1531 // Login failed
1532  
1533 // Make sure a usable error key is set
1534 if (data.Reason != String.Empty)
1535 InternalErrorKey = data.Reason;
1536 else
1537 InternalErrorKey = "unknown";
1538  
1539 UpdateLoginStatus(LoginStatus.Failed, data.Message);
1540 }
1541 }
1542 else
1543 {
1544 // Got an LLSD map but no login value
1545 UpdateLoginStatus(LoginStatus.Failed, "login parameter missing in the response");
1546 }
1547 }
1548 else
1549 {
1550 // No LLSD response
1551 InternalErrorKey = "bad response";
1552 UpdateLoginStatus(LoginStatus.Failed, "Empty or unparseable login response");
1553 }
1554 }
1555 else
1556 {
1557 // Connection error
1558 InternalErrorKey = "no connection";
1559 UpdateLoginStatus(LoginStatus.Failed, error.Message);
1560 }
1561 }
1562  
1563 /// <summary>
1564 /// Get current OS
1565 /// </summary>
1566 /// <returns>Either "Win" or "Linux"</returns>
1567 public static string GetPlatform()
1568 {
1569 switch (Environment.OSVersion.Platform)
1570 {
1571 case PlatformID.Unix:
1572 return "Linux";
1573 default:
1574 return "Win";
1575 }
1576 }
1577  
1578 /// <summary>
1579 /// Get clients default Mac Address
1580 /// </summary>
1581 /// <returns>A string containing the first found Mac Address</returns>
1582 public static string GetMAC()
1583 {
1584 string mac = String.Empty;
1585  
1586 try
1587 {
1588 System.Net.NetworkInformation.NetworkInterface[] nics = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();
1589  
1590 if (nics != null && nics.Length > 0)
1591 {
1592 for (int i = 0; i < nics.Length; i++)
1593 {
1594 string adapterMac = nics[i].GetPhysicalAddress().ToString().ToUpper();
1595 if (adapterMac.Length == 12 && adapterMac != "000000000000")
1596 {
1597 mac = adapterMac;
1598 continue;
1599 }
1600 }
1601 }
1602 }
1603 catch { }
1604  
1605 if (mac.Length < 12)
1606 mac = UUID.Random().ToString().Substring(24, 12);
1607  
1608 return String.Format("{0}:{1}:{2}:{3}:{4}:{5}",
1609 mac.Substring(0, 2),
1610 mac.Substring(2, 2),
1611 mac.Substring(4, 2),
1612 mac.Substring(6, 2),
1613 mac.Substring(8, 2),
1614 mac.Substring(10, 2));
1615 }
1616  
1617 #endregion
1618 }
1619 #region EventArgs
1620  
1621 public class LoginProgressEventArgs : EventArgs
1622 {
1623 private readonly LoginStatus m_Status;
1624 private readonly String m_Message;
1625 private readonly String m_FailReason;
1626  
1627 public LoginStatus Status { get { return m_Status; } }
1628 public String Message { get { return m_Message; } }
1629 public string FailReason { get { return m_FailReason; } }
1630  
1631 public LoginProgressEventArgs(LoginStatus login, String message, String failReason)
1632 {
1633 this.m_Status = login;
1634 this.m_Message = message;
1635 this.m_FailReason = failReason;
1636 }
1637 }
1638  
1639 #endregion EventArgs
1640 }