corrade-vassal – Blame information for rev 1
?pathlinks?
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&128&152&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 | } |