diff options
author | UbitUmarov | 2015-09-01 11:43:07 +0100 |
---|---|---|
committer | UbitUmarov | 2015-09-01 11:43:07 +0100 |
commit | fb78b182520fc9bb0f971afd0322029c70278ea6 (patch) | |
tree | b4e30d383938fdeef8c92d1d1c2f44bb61d329bd /OpenSim/Services/HypergridService | |
parent | lixo (diff) | |
parent | Mantis #7713: fixed bug introduced by 1st MOSES patch. (diff) | |
download | opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.zip opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.tar.gz opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.tar.bz2 opensim-SC-fb78b182520fc9bb0f971afd0322029c70278ea6.tar.xz |
Merge remote-tracking branch 'os/master'
Diffstat (limited to 'OpenSim/Services/HypergridService')
11 files changed, 3673 insertions, 0 deletions
diff --git a/OpenSim/Services/HypergridService/GatekeeperService.cs b/OpenSim/Services/HypergridService/GatekeeperService.cs new file mode 100644 index 0000000..44b26d5 --- /dev/null +++ b/OpenSim/Services/HypergridService/GatekeeperService.cs | |||
@@ -0,0 +1,559 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Net; | ||
31 | using System.Reflection; | ||
32 | using System.Text.RegularExpressions; | ||
33 | |||
34 | using OpenSim.Framework; | ||
35 | using OpenSim.Services.Interfaces; | ||
36 | using GridRegion = OpenSim.Services.Interfaces.GridRegion; | ||
37 | using OpenSim.Server.Base; | ||
38 | using OpenSim.Services.Connectors.Hypergrid; | ||
39 | |||
40 | using OpenMetaverse; | ||
41 | |||
42 | using Nini.Config; | ||
43 | using log4net; | ||
44 | |||
45 | namespace OpenSim.Services.HypergridService | ||
46 | { | ||
47 | public class GatekeeperService : IGatekeeperService | ||
48 | { | ||
49 | private static readonly ILog m_log = | ||
50 | LogManager.GetLogger( | ||
51 | MethodBase.GetCurrentMethod().DeclaringType); | ||
52 | |||
53 | private static bool m_Initialized = false; | ||
54 | |||
55 | private static IGridService m_GridService; | ||
56 | private static IPresenceService m_PresenceService; | ||
57 | private static IUserAccountService m_UserAccountService; | ||
58 | private static IUserAgentService m_UserAgentService; | ||
59 | private static ISimulationService m_SimulationService; | ||
60 | private static IGridUserService m_GridUserService; | ||
61 | private static IBansService m_BansService; | ||
62 | |||
63 | private static string m_AllowedClients = string.Empty; | ||
64 | private static string m_DeniedClients = string.Empty; | ||
65 | private static bool m_ForeignAgentsAllowed = true; | ||
66 | private static List<string> m_ForeignsAllowedExceptions = new List<string>(); | ||
67 | private static List<string> m_ForeignsDisallowedExceptions = new List<string>(); | ||
68 | |||
69 | private static UUID m_ScopeID; | ||
70 | private static bool m_AllowTeleportsToAnyRegion; | ||
71 | private static string m_ExternalName; | ||
72 | private static Uri m_Uri; | ||
73 | private static GridRegion m_DefaultGatewayRegion; | ||
74 | |||
75 | public GatekeeperService(IConfigSource config, ISimulationService simService) | ||
76 | { | ||
77 | if (!m_Initialized) | ||
78 | { | ||
79 | m_Initialized = true; | ||
80 | |||
81 | IConfig serverConfig = config.Configs["GatekeeperService"]; | ||
82 | if (serverConfig == null) | ||
83 | throw new Exception(String.Format("No section GatekeeperService in config file")); | ||
84 | |||
85 | string accountService = serverConfig.GetString("UserAccountService", String.Empty); | ||
86 | string homeUsersService = serverConfig.GetString("UserAgentService", string.Empty); | ||
87 | string gridService = serverConfig.GetString("GridService", String.Empty); | ||
88 | string presenceService = serverConfig.GetString("PresenceService", String.Empty); | ||
89 | string simulationService = serverConfig.GetString("SimulationService", String.Empty); | ||
90 | string gridUserService = serverConfig.GetString("GridUserService", String.Empty); | ||
91 | string bansService = serverConfig.GetString("BansService", String.Empty); | ||
92 | |||
93 | // These are mandatory, the others aren't | ||
94 | if (gridService == string.Empty || presenceService == string.Empty) | ||
95 | throw new Exception("Incomplete specifications, Gatekeeper Service cannot function."); | ||
96 | |||
97 | string scope = serverConfig.GetString("ScopeID", UUID.Zero.ToString()); | ||
98 | UUID.TryParse(scope, out m_ScopeID); | ||
99 | //m_WelcomeMessage = serverConfig.GetString("WelcomeMessage", "Welcome to OpenSim!"); | ||
100 | m_AllowTeleportsToAnyRegion = serverConfig.GetBoolean("AllowTeleportsToAnyRegion", true); | ||
101 | m_ExternalName = Util.GetConfigVarFromSections<string>(config, "GatekeeperURI", | ||
102 | new string[] { "Startup", "Hypergrid", "GatekeeperService" }, String.Empty); | ||
103 | m_ExternalName = serverConfig.GetString("ExternalName", m_ExternalName); | ||
104 | if (m_ExternalName != string.Empty && !m_ExternalName.EndsWith("/")) | ||
105 | m_ExternalName = m_ExternalName + "/"; | ||
106 | |||
107 | try | ||
108 | { | ||
109 | m_Uri = new Uri(m_ExternalName); | ||
110 | } | ||
111 | catch | ||
112 | { | ||
113 | m_log.WarnFormat("[GATEKEEPER SERVICE]: Malformed gatekeeper address {0}", m_ExternalName); | ||
114 | } | ||
115 | |||
116 | Object[] args = new Object[] { config }; | ||
117 | m_GridService = ServerUtils.LoadPlugin<IGridService>(gridService, args); | ||
118 | m_PresenceService = ServerUtils.LoadPlugin<IPresenceService>(presenceService, args); | ||
119 | |||
120 | if (accountService != string.Empty) | ||
121 | m_UserAccountService = ServerUtils.LoadPlugin<IUserAccountService>(accountService, args); | ||
122 | if (homeUsersService != string.Empty) | ||
123 | m_UserAgentService = ServerUtils.LoadPlugin<IUserAgentService>(homeUsersService, args); | ||
124 | if (gridUserService != string.Empty) | ||
125 | m_GridUserService = ServerUtils.LoadPlugin<IGridUserService>(gridUserService, args); | ||
126 | if (bansService != string.Empty) | ||
127 | m_BansService = ServerUtils.LoadPlugin<IBansService>(bansService, args); | ||
128 | |||
129 | if (simService != null) | ||
130 | m_SimulationService = simService; | ||
131 | else if (simulationService != string.Empty) | ||
132 | m_SimulationService = ServerUtils.LoadPlugin<ISimulationService>(simulationService, args); | ||
133 | |||
134 | m_AllowedClients = serverConfig.GetString("AllowedClients", string.Empty); | ||
135 | m_DeniedClients = serverConfig.GetString("DeniedClients", string.Empty); | ||
136 | m_ForeignAgentsAllowed = serverConfig.GetBoolean("ForeignAgentsAllowed", true); | ||
137 | |||
138 | LoadDomainExceptionsFromConfig(serverConfig, "AllowExcept", m_ForeignsAllowedExceptions); | ||
139 | LoadDomainExceptionsFromConfig(serverConfig, "DisallowExcept", m_ForeignsDisallowedExceptions); | ||
140 | |||
141 | if (m_GridService == null || m_PresenceService == null || m_SimulationService == null) | ||
142 | throw new Exception("Unable to load a required plugin, Gatekeeper Service cannot function."); | ||
143 | |||
144 | m_log.Debug("[GATEKEEPER SERVICE]: Starting..."); | ||
145 | } | ||
146 | } | ||
147 | |||
148 | public GatekeeperService(IConfigSource config) | ||
149 | : this(config, null) | ||
150 | { | ||
151 | } | ||
152 | |||
153 | protected void LoadDomainExceptionsFromConfig(IConfig config, string variable, List<string> exceptions) | ||
154 | { | ||
155 | string value = config.GetString(variable, string.Empty); | ||
156 | string[] parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); | ||
157 | |||
158 | foreach (string s in parts) | ||
159 | exceptions.Add(s.Trim()); | ||
160 | } | ||
161 | |||
162 | public bool LinkRegion(string regionName, out UUID regionID, out ulong regionHandle, out string externalName, out string imageURL, out string reason) | ||
163 | { | ||
164 | regionID = UUID.Zero; | ||
165 | regionHandle = 0; | ||
166 | externalName = m_ExternalName + ((regionName != string.Empty) ? " " + regionName : ""); | ||
167 | imageURL = string.Empty; | ||
168 | reason = string.Empty; | ||
169 | GridRegion region = null; | ||
170 | |||
171 | m_log.DebugFormat("[GATEKEEPER SERVICE]: Request to link to {0}", (regionName == string.Empty)? "default region" : regionName); | ||
172 | if (!m_AllowTeleportsToAnyRegion || regionName == string.Empty) | ||
173 | { | ||
174 | List<GridRegion> defs = m_GridService.GetDefaultHypergridRegions(m_ScopeID); | ||
175 | if (defs != null && defs.Count > 0) | ||
176 | { | ||
177 | region = defs[0]; | ||
178 | m_DefaultGatewayRegion = region; | ||
179 | } | ||
180 | else | ||
181 | { | ||
182 | reason = "Grid setup problem. Try specifying a particular region here."; | ||
183 | m_log.DebugFormat("[GATEKEEPER SERVICE]: Unable to send information. Please specify a default region for this grid!"); | ||
184 | return false; | ||
185 | } | ||
186 | } | ||
187 | else | ||
188 | { | ||
189 | region = m_GridService.GetRegionByName(m_ScopeID, regionName); | ||
190 | if (region == null) | ||
191 | { | ||
192 | reason = "Region not found"; | ||
193 | return false; | ||
194 | } | ||
195 | } | ||
196 | |||
197 | regionID = region.RegionID; | ||
198 | regionHandle = region.RegionHandle; | ||
199 | |||
200 | string regionimage = "regionImage" + regionID.ToString(); | ||
201 | regionimage = regionimage.Replace("-", ""); | ||
202 | imageURL = region.ServerURI + "index.php?method=" + regionimage; | ||
203 | |||
204 | return true; | ||
205 | } | ||
206 | |||
207 | public GridRegion GetHyperlinkRegion(UUID regionID, UUID agentID, string agentHomeURI, out string message) | ||
208 | { | ||
209 | message = null; | ||
210 | |||
211 | if (!m_AllowTeleportsToAnyRegion) | ||
212 | { | ||
213 | // Don't even check the given regionID | ||
214 | m_log.DebugFormat( | ||
215 | "[GATEKEEPER SERVICE]: Returning gateway region {0} {1} @ {2} to user {3}{4} as teleporting to arbitrary regions is not allowed.", | ||
216 | m_DefaultGatewayRegion.RegionName, | ||
217 | m_DefaultGatewayRegion.RegionID, | ||
218 | m_DefaultGatewayRegion.ServerURI, | ||
219 | agentID, | ||
220 | agentHomeURI == null ? "" : " @ " + agentHomeURI); | ||
221 | |||
222 | message = "Teleporting to the default region."; | ||
223 | return m_DefaultGatewayRegion; | ||
224 | } | ||
225 | |||
226 | GridRegion region = m_GridService.GetRegionByUUID(m_ScopeID, regionID); | ||
227 | |||
228 | if (region == null) | ||
229 | { | ||
230 | m_log.DebugFormat( | ||
231 | "[GATEKEEPER SERVICE]: Could not find region with ID {0} as requested by user {1}{2}. Returning null.", | ||
232 | regionID, agentID, (agentHomeURI == null) ? "" : " @ " + agentHomeURI); | ||
233 | |||
234 | message = "The teleport destination could not be found."; | ||
235 | return null; | ||
236 | } | ||
237 | |||
238 | m_log.DebugFormat( | ||
239 | "[GATEKEEPER SERVICE]: Returning region {0} {1} @ {2} to user {3}{4}.", | ||
240 | region.RegionName, | ||
241 | region.RegionID, | ||
242 | region.ServerURI, | ||
243 | agentID, | ||
244 | agentHomeURI == null ? "" : " @ " + agentHomeURI); | ||
245 | |||
246 | return region; | ||
247 | } | ||
248 | |||
249 | #region Login Agent | ||
250 | public bool LoginAgent(GridRegion source, AgentCircuitData aCircuit, GridRegion destination, out string reason) | ||
251 | { | ||
252 | reason = string.Empty; | ||
253 | |||
254 | string authURL = string.Empty; | ||
255 | if (aCircuit.ServiceURLs.ContainsKey("HomeURI")) | ||
256 | authURL = aCircuit.ServiceURLs["HomeURI"].ToString(); | ||
257 | |||
258 | m_log.InfoFormat("[GATEKEEPER SERVICE]: Login request for {0} {1} @ {2} ({3}) at {4} using viewer {5}, channel {6}, IP {7}, Mac {8}, Id0 {9}, Teleport Flags: {10}. From region {11}", | ||
259 | aCircuit.firstname, aCircuit.lastname, authURL, aCircuit.AgentID, destination.RegionID, | ||
260 | aCircuit.Viewer, aCircuit.Channel, aCircuit.IPAddress, aCircuit.Mac, aCircuit.Id0, (TeleportFlags)aCircuit.teleportFlags, | ||
261 | (source == null) ? "Unknown" : string.Format("{0} ({1}){2}", source.RegionName, source.RegionID, (source.RawServerURI == null) ? "" : " @ " + source.ServerURI)); | ||
262 | |||
263 | string curViewer = Util.GetViewerName(aCircuit); | ||
264 | |||
265 | // | ||
266 | // Check client | ||
267 | // | ||
268 | if (m_AllowedClients != string.Empty) | ||
269 | { | ||
270 | Regex arx = new Regex(m_AllowedClients); | ||
271 | Match am = arx.Match(curViewer); | ||
272 | |||
273 | if (!am.Success) | ||
274 | { | ||
275 | m_log.InfoFormat("[GATEKEEPER SERVICE]: Login failed, reason: client {0} is not allowed", curViewer); | ||
276 | return false; | ||
277 | } | ||
278 | } | ||
279 | |||
280 | if (m_DeniedClients != string.Empty) | ||
281 | { | ||
282 | Regex drx = new Regex(m_DeniedClients); | ||
283 | Match dm = drx.Match(curViewer); | ||
284 | |||
285 | if (dm.Success) | ||
286 | { | ||
287 | m_log.InfoFormat("[GATEKEEPER SERVICE]: Login failed, reason: client {0} is denied", curViewer); | ||
288 | return false; | ||
289 | } | ||
290 | } | ||
291 | |||
292 | // | ||
293 | // Authenticate the user | ||
294 | // | ||
295 | if (!Authenticate(aCircuit)) | ||
296 | { | ||
297 | reason = "Unable to verify identity"; | ||
298 | m_log.InfoFormat("[GATEKEEPER SERVICE]: Unable to verify identity of agent {0} {1}. Refusing service.", aCircuit.firstname, aCircuit.lastname); | ||
299 | return false; | ||
300 | } | ||
301 | m_log.DebugFormat("[GATEKEEPER SERVICE]: Identity verified for {0} {1} @ {2}", aCircuit.firstname, aCircuit.lastname, authURL); | ||
302 | |||
303 | // | ||
304 | // Check for impersonations | ||
305 | // | ||
306 | UserAccount account = null; | ||
307 | if (m_UserAccountService != null) | ||
308 | { | ||
309 | // Check to see if we have a local user with that UUID | ||
310 | account = m_UserAccountService.GetUserAccount(m_ScopeID, aCircuit.AgentID); | ||
311 | if (account != null) | ||
312 | { | ||
313 | // Make sure this is the user coming home, and not a foreign user with same UUID as a local user | ||
314 | if (m_UserAgentService != null) | ||
315 | { | ||
316 | if (!m_UserAgentService.IsAgentComingHome(aCircuit.SessionID, m_ExternalName)) | ||
317 | { | ||
318 | // Can't do, sorry | ||
319 | reason = "Unauthorized"; | ||
320 | m_log.InfoFormat("[GATEKEEPER SERVICE]: Foreign agent {0} {1} has same ID as local user. Refusing service.", | ||
321 | aCircuit.firstname, aCircuit.lastname); | ||
322 | return false; | ||
323 | |||
324 | } | ||
325 | } | ||
326 | } | ||
327 | } | ||
328 | |||
329 | // | ||
330 | // Foreign agents allowed? Exceptions? | ||
331 | // | ||
332 | if (account == null) | ||
333 | { | ||
334 | bool allowed = m_ForeignAgentsAllowed; | ||
335 | |||
336 | if (m_ForeignAgentsAllowed && IsException(aCircuit, m_ForeignsAllowedExceptions)) | ||
337 | allowed = false; | ||
338 | |||
339 | if (!m_ForeignAgentsAllowed && IsException(aCircuit, m_ForeignsDisallowedExceptions)) | ||
340 | allowed = true; | ||
341 | |||
342 | if (!allowed) | ||
343 | { | ||
344 | reason = "Destination does not allow visitors from your world"; | ||
345 | m_log.InfoFormat("[GATEKEEPER SERVICE]: Foreign agents are not permitted {0} {1} @ {2}. Refusing service.", | ||
346 | aCircuit.firstname, aCircuit.lastname, aCircuit.ServiceURLs["HomeURI"]); | ||
347 | return false; | ||
348 | } | ||
349 | } | ||
350 | |||
351 | // | ||
352 | // Is the user banned? | ||
353 | // This uses a Ban service that's more powerful than the configs | ||
354 | // | ||
355 | string uui = (account != null ? aCircuit.AgentID.ToString() : Util.ProduceUserUniversalIdentifier(aCircuit)); | ||
356 | if (m_BansService != null && m_BansService.IsBanned(uui, aCircuit.IPAddress, aCircuit.Id0, authURL)) | ||
357 | { | ||
358 | reason = "You are banned from this world"; | ||
359 | m_log.InfoFormat("[GATEKEEPER SERVICE]: Login failed, reason: user {0} is banned", uui); | ||
360 | return false; | ||
361 | } | ||
362 | |||
363 | m_log.DebugFormat("[GATEKEEPER SERVICE]: User {0} is ok", aCircuit.Name); | ||
364 | |||
365 | bool isFirstLogin = false; | ||
366 | // | ||
367 | // Login the presence, if it's not there yet (by the login service) | ||
368 | // | ||
369 | PresenceInfo presence = m_PresenceService.GetAgent(aCircuit.SessionID); | ||
370 | if (presence != null) // it has been placed there by the login service | ||
371 | isFirstLogin = true; | ||
372 | |||
373 | else | ||
374 | { | ||
375 | if (!m_PresenceService.LoginAgent(aCircuit.AgentID.ToString(), aCircuit.SessionID, aCircuit.SecureSessionID)) | ||
376 | { | ||
377 | reason = "Unable to login presence"; | ||
378 | m_log.InfoFormat("[GATEKEEPER SERVICE]: Presence login failed for foreign agent {0} {1}. Refusing service.", | ||
379 | aCircuit.firstname, aCircuit.lastname); | ||
380 | return false; | ||
381 | } | ||
382 | |||
383 | m_log.DebugFormat("[GATEKEEPER SERVICE]: Login presence {0} is ok", aCircuit.Name); | ||
384 | |||
385 | // Also login foreigners with GridUser service | ||
386 | if (m_GridUserService != null && account == null) | ||
387 | { | ||
388 | string userId = aCircuit.AgentID.ToString(); | ||
389 | string first = aCircuit.firstname, last = aCircuit.lastname; | ||
390 | if (last.StartsWith("@")) | ||
391 | { | ||
392 | string[] parts = aCircuit.firstname.Split('.'); | ||
393 | if (parts.Length >= 2) | ||
394 | { | ||
395 | first = parts[0]; | ||
396 | last = parts[1]; | ||
397 | } | ||
398 | } | ||
399 | |||
400 | userId += ";" + aCircuit.ServiceURLs["HomeURI"] + ";" + first + " " + last; | ||
401 | m_GridUserService.LoggedIn(userId); | ||
402 | } | ||
403 | } | ||
404 | |||
405 | // | ||
406 | // Get the region | ||
407 | // | ||
408 | destination = m_GridService.GetRegionByUUID(m_ScopeID, destination.RegionID); | ||
409 | if (destination == null) | ||
410 | { | ||
411 | reason = "Destination region not found"; | ||
412 | return false; | ||
413 | } | ||
414 | |||
415 | m_log.DebugFormat( | ||
416 | "[GATEKEEPER SERVICE]: Destination {0} is ok for {1}", destination.RegionName, aCircuit.Name); | ||
417 | |||
418 | // | ||
419 | // Adjust the visible name | ||
420 | // | ||
421 | if (account != null) | ||
422 | { | ||
423 | aCircuit.firstname = account.FirstName; | ||
424 | aCircuit.lastname = account.LastName; | ||
425 | } | ||
426 | if (account == null) | ||
427 | { | ||
428 | if (!aCircuit.lastname.StartsWith("@")) | ||
429 | aCircuit.firstname = aCircuit.firstname + "." + aCircuit.lastname; | ||
430 | try | ||
431 | { | ||
432 | Uri uri = new Uri(aCircuit.ServiceURLs["HomeURI"].ToString()); | ||
433 | aCircuit.lastname = "@" + uri.Authority; | ||
434 | } | ||
435 | catch | ||
436 | { | ||
437 | m_log.WarnFormat("[GATEKEEPER SERVICE]: Malformed HomeURI (this should never happen): {0}", aCircuit.ServiceURLs["HomeURI"]); | ||
438 | aCircuit.lastname = "@" + aCircuit.ServiceURLs["HomeURI"].ToString(); | ||
439 | } | ||
440 | } | ||
441 | |||
442 | // | ||
443 | // Finally launch the agent at the destination | ||
444 | // | ||
445 | Constants.TeleportFlags loginFlag = isFirstLogin ? Constants.TeleportFlags.ViaLogin : Constants.TeleportFlags.ViaHGLogin; | ||
446 | |||
447 | // Preserve our TeleportFlags we have gathered so-far | ||
448 | loginFlag |= (Constants.TeleportFlags) aCircuit.teleportFlags; | ||
449 | |||
450 | m_log.DebugFormat("[GATEKEEPER SERVICE]: Launching {0}, Teleport Flags: {1}", aCircuit.Name, loginFlag); | ||
451 | |||
452 | string version; | ||
453 | |||
454 | if (!m_SimulationService.QueryAccess( | ||
455 | destination, aCircuit.AgentID, aCircuit.ServiceURLs["HomeURI"].ToString(), | ||
456 | true, aCircuit.startpos, "SIMULATION/0.3", new List<UUID>(), out version, out reason)) | ||
457 | return false; | ||
458 | |||
459 | return m_SimulationService.CreateAgent(source, destination, aCircuit, (uint)loginFlag, out reason); | ||
460 | } | ||
461 | |||
462 | protected bool Authenticate(AgentCircuitData aCircuit) | ||
463 | { | ||
464 | if (!CheckAddress(aCircuit.ServiceSessionID)) | ||
465 | return false; | ||
466 | |||
467 | if (string.IsNullOrEmpty(aCircuit.IPAddress)) | ||
468 | { | ||
469 | m_log.DebugFormat("[GATEKEEPER SERVICE]: Agent did not provide a client IP address."); | ||
470 | return false; | ||
471 | } | ||
472 | |||
473 | string userURL = string.Empty; | ||
474 | if (aCircuit.ServiceURLs.ContainsKey("HomeURI")) | ||
475 | userURL = aCircuit.ServiceURLs["HomeURI"].ToString(); | ||
476 | |||
477 | if (userURL == string.Empty) | ||
478 | { | ||
479 | m_log.DebugFormat("[GATEKEEPER SERVICE]: Agent did not provide an authentication server URL"); | ||
480 | return false; | ||
481 | } | ||
482 | |||
483 | if (userURL == m_ExternalName) | ||
484 | { | ||
485 | return m_UserAgentService.VerifyAgent(aCircuit.SessionID, aCircuit.ServiceSessionID); | ||
486 | } | ||
487 | else | ||
488 | { | ||
489 | IUserAgentService userAgentService = new UserAgentServiceConnector(userURL); | ||
490 | |||
491 | try | ||
492 | { | ||
493 | return userAgentService.VerifyAgent(aCircuit.SessionID, aCircuit.ServiceSessionID); | ||
494 | } | ||
495 | catch | ||
496 | { | ||
497 | m_log.DebugFormat("[GATEKEEPER SERVICE]: Unable to contact authentication service at {0}", userURL); | ||
498 | return false; | ||
499 | } | ||
500 | } | ||
501 | } | ||
502 | |||
503 | // Check that the service token was generated for *this* grid. | ||
504 | // If it wasn't then that's a fake agent. | ||
505 | protected bool CheckAddress(string serviceToken) | ||
506 | { | ||
507 | string[] parts = serviceToken.Split(new char[] { ';' }); | ||
508 | if (parts.Length < 2) | ||
509 | return false; | ||
510 | |||
511 | char[] trailing_slash = new char[] { '/' }; | ||
512 | string addressee = parts[0].TrimEnd(trailing_slash); | ||
513 | string externalname = m_ExternalName.TrimEnd(trailing_slash); | ||
514 | m_log.DebugFormat("[GATEKEEPER SERVICE]: Verifying {0} against {1}", addressee, externalname); | ||
515 | |||
516 | Uri uri; | ||
517 | try | ||
518 | { | ||
519 | uri = new Uri(addressee); | ||
520 | } | ||
521 | catch | ||
522 | { | ||
523 | m_log.DebugFormat("[GATEKEEPER SERVICE]: Visitor provided malformed service address {0}", addressee); | ||
524 | return false; | ||
525 | } | ||
526 | |||
527 | return string.Equals(uri.GetLeftPart(UriPartial.Authority), m_Uri.GetLeftPart(UriPartial.Authority), StringComparison.OrdinalIgnoreCase) ; | ||
528 | } | ||
529 | |||
530 | #endregion | ||
531 | |||
532 | |||
533 | #region Misc | ||
534 | |||
535 | private bool IsException(AgentCircuitData aCircuit, List<string> exceptions) | ||
536 | { | ||
537 | bool exception = false; | ||
538 | if (exceptions.Count > 0) // we have exceptions | ||
539 | { | ||
540 | // Retrieve the visitor's origin | ||
541 | string userURL = aCircuit.ServiceURLs["HomeURI"].ToString(); | ||
542 | if (!userURL.EndsWith("/")) | ||
543 | userURL += "/"; | ||
544 | |||
545 | if (exceptions.Find(delegate(string s) | ||
546 | { | ||
547 | if (!s.EndsWith("/")) | ||
548 | s += "/"; | ||
549 | return s == userURL; | ||
550 | }) != null) | ||
551 | exception = true; | ||
552 | } | ||
553 | |||
554 | return exception; | ||
555 | } | ||
556 | |||
557 | #endregion | ||
558 | } | ||
559 | } | ||
diff --git a/OpenSim/Services/HypergridService/HGAssetService.cs b/OpenSim/Services/HypergridService/HGAssetService.cs new file mode 100644 index 0000000..b83fb1e --- /dev/null +++ b/OpenSim/Services/HypergridService/HGAssetService.cs | |||
@@ -0,0 +1,193 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | using System; | ||
28 | using System.Collections.Generic; | ||
29 | using System.IO; | ||
30 | using System.Reflection; | ||
31 | using System.Xml; | ||
32 | |||
33 | using Nini.Config; | ||
34 | using log4net; | ||
35 | using OpenMetaverse; | ||
36 | |||
37 | using OpenSim.Framework; | ||
38 | using OpenSim.Framework.Serialization.External; | ||
39 | using OpenSim.Server.Base; | ||
40 | using OpenSim.Services.Interfaces; | ||
41 | using OpenSim.Services.AssetService; | ||
42 | |||
43 | namespace OpenSim.Services.HypergridService | ||
44 | { | ||
45 | /// <summary> | ||
46 | /// Hypergrid asset service. It serves the IAssetService interface, | ||
47 | /// but implements it in ways that are appropriate for inter-grid | ||
48 | /// asset exchanges. | ||
49 | /// </summary> | ||
50 | public class HGAssetService : OpenSim.Services.AssetService.AssetService, IAssetService | ||
51 | { | ||
52 | private static readonly ILog m_log = | ||
53 | LogManager.GetLogger( | ||
54 | MethodBase.GetCurrentMethod().DeclaringType); | ||
55 | |||
56 | private string m_HomeURL; | ||
57 | private IUserAccountService m_UserAccountService; | ||
58 | |||
59 | private UserAccountCache m_Cache; | ||
60 | |||
61 | private AssetPermissions m_AssetPerms; | ||
62 | |||
63 | public HGAssetService(IConfigSource config, string configName) : base(config, configName) | ||
64 | { | ||
65 | m_log.Debug("[HGAsset Service]: Starting"); | ||
66 | IConfig assetConfig = config.Configs[configName]; | ||
67 | if (assetConfig == null) | ||
68 | throw new Exception("No HGAssetService configuration"); | ||
69 | |||
70 | string userAccountsDll = assetConfig.GetString("UserAccountsService", string.Empty); | ||
71 | if (userAccountsDll == string.Empty) | ||
72 | throw new Exception("Please specify UserAccountsService in HGAssetService configuration"); | ||
73 | |||
74 | Object[] args = new Object[] { config }; | ||
75 | m_UserAccountService = ServerUtils.LoadPlugin<IUserAccountService>(userAccountsDll, args); | ||
76 | if (m_UserAccountService == null) | ||
77 | throw new Exception(String.Format("Unable to create UserAccountService from {0}", userAccountsDll)); | ||
78 | |||
79 | m_HomeURL = Util.GetConfigVarFromSections<string>(config, "HomeURI", | ||
80 | new string[] { "Startup", "Hypergrid", configName }, string.Empty); | ||
81 | if (m_HomeURL == string.Empty) | ||
82 | throw new Exception("[HGAssetService] No HomeURI specified"); | ||
83 | |||
84 | m_Cache = UserAccountCache.CreateUserAccountCache(m_UserAccountService); | ||
85 | |||
86 | // Permissions | ||
87 | m_AssetPerms = new AssetPermissions(assetConfig); | ||
88 | |||
89 | } | ||
90 | |||
91 | #region IAssetService overrides | ||
92 | public override AssetBase Get(string id) | ||
93 | { | ||
94 | AssetBase asset = base.Get(id); | ||
95 | |||
96 | if (asset == null) | ||
97 | return null; | ||
98 | |||
99 | if (!m_AssetPerms.AllowedExport(asset.Type)) | ||
100 | return null; | ||
101 | |||
102 | if (asset.Metadata.Type == (sbyte)AssetType.Object) | ||
103 | asset.Data = AdjustIdentifiers(asset.Data); | ||
104 | |||
105 | AdjustIdentifiers(asset.Metadata); | ||
106 | |||
107 | return asset; | ||
108 | } | ||
109 | |||
110 | public override AssetMetadata GetMetadata(string id) | ||
111 | { | ||
112 | AssetMetadata meta = base.GetMetadata(id); | ||
113 | |||
114 | if (meta == null) | ||
115 | return null; | ||
116 | |||
117 | AdjustIdentifiers(meta); | ||
118 | |||
119 | return meta; | ||
120 | } | ||
121 | |||
122 | public override byte[] GetData(string id) | ||
123 | { | ||
124 | AssetBase asset = Get(id); | ||
125 | |||
126 | if (asset == null) | ||
127 | return null; | ||
128 | |||
129 | if (!m_AssetPerms.AllowedExport(asset.Type)) | ||
130 | return null; | ||
131 | |||
132 | // Deal with bug introduced in Oct. 20 (1eb3e6cc43e2a7b4053bc1185c7c88e22356c5e8) | ||
133 | // Fix bad assets before sending them elsewhere | ||
134 | if (asset.Type == (int)AssetType.Object && asset.Data != null) | ||
135 | { | ||
136 | string xml = ExternalRepresentationUtils.SanitizeXml(Utils.BytesToString(asset.Data)); | ||
137 | asset.Data = Utils.StringToBytes(xml); | ||
138 | } | ||
139 | |||
140 | return asset.Data; | ||
141 | } | ||
142 | |||
143 | //public virtual bool Get(string id, Object sender, AssetRetrieved handler) | ||
144 | |||
145 | public override string Store(AssetBase asset) | ||
146 | { | ||
147 | if (!m_AssetPerms.AllowedImport(asset.Type)) | ||
148 | return string.Empty; | ||
149 | |||
150 | // Deal with bug introduced in Oct. 20 (1eb3e6cc43e2a7b4053bc1185c7c88e22356c5e8) | ||
151 | // Fix bad assets before storing on this server | ||
152 | if (asset.Type == (int)AssetType.Object && asset.Data != null) | ||
153 | { | ||
154 | string xml = ExternalRepresentationUtils.SanitizeXml(Utils.BytesToString(asset.Data)); | ||
155 | asset.Data = Utils.StringToBytes(xml); | ||
156 | } | ||
157 | |||
158 | return base.Store(asset); | ||
159 | } | ||
160 | |||
161 | public override bool Delete(string id) | ||
162 | { | ||
163 | // NOGO | ||
164 | return false; | ||
165 | } | ||
166 | |||
167 | #endregion | ||
168 | |||
169 | protected void AdjustIdentifiers(AssetMetadata meta) | ||
170 | { | ||
171 | if (meta == null || m_Cache == null) | ||
172 | return; | ||
173 | |||
174 | UserAccount creator = m_Cache.GetUser(meta.CreatorID); | ||
175 | if (creator != null) | ||
176 | meta.CreatorID = meta.CreatorID + ";" + m_HomeURL + "/" + creator.FirstName + " " + creator.LastName; | ||
177 | } | ||
178 | |||
179 | // Only for Object | ||
180 | protected byte[] AdjustIdentifiers(byte[] data) | ||
181 | { | ||
182 | string xml = Utils.BytesToString(data); | ||
183 | |||
184 | // Deal with bug introduced in Oct. 20 (1eb3e6cc43e2a7b4053bc1185c7c88e22356c5e8) | ||
185 | // Fix bad assets before sending them elsewhere | ||
186 | xml = ExternalRepresentationUtils.SanitizeXml(xml); | ||
187 | |||
188 | return Utils.StringToBytes(ExternalRepresentationUtils.RewriteSOP(xml, "HGAssetService", m_HomeURL, m_Cache, UUID.Zero)); | ||
189 | } | ||
190 | |||
191 | } | ||
192 | |||
193 | } | ||
diff --git a/OpenSim/Services/HypergridService/HGFSAssetService.cs b/OpenSim/Services/HypergridService/HGFSAssetService.cs new file mode 100644 index 0000000..54e8ccb --- /dev/null +++ b/OpenSim/Services/HypergridService/HGFSAssetService.cs | |||
@@ -0,0 +1,189 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | using System; | ||
28 | using System.Reflection; | ||
29 | |||
30 | using Nini.Config; | ||
31 | using log4net; | ||
32 | using OpenMetaverse; | ||
33 | |||
34 | using OpenSim.Framework; | ||
35 | using OpenSim.Framework.Serialization.External; | ||
36 | using OpenSim.Server.Base; | ||
37 | using OpenSim.Services.Interfaces; | ||
38 | using OpenSim.Services.FSAssetService; | ||
39 | |||
40 | namespace OpenSim.Services.HypergridService | ||
41 | { | ||
42 | /// <summary> | ||
43 | /// Hypergrid asset service. It serves the IAssetService interface, | ||
44 | /// but implements it in ways that are appropriate for inter-grid | ||
45 | /// asset exchanges. This version is for FSAssets. | ||
46 | /// </summary> | ||
47 | public class HGFSAssetService : FSAssetConnector, IAssetService | ||
48 | { | ||
49 | private static readonly ILog m_log = | ||
50 | LogManager.GetLogger( | ||
51 | MethodBase.GetCurrentMethod().DeclaringType); | ||
52 | |||
53 | private string m_HomeURL; | ||
54 | private IUserAccountService m_UserAccountService; | ||
55 | |||
56 | private UserAccountCache m_Cache; | ||
57 | |||
58 | private AssetPermissions m_AssetPerms; | ||
59 | |||
60 | public HGFSAssetService(IConfigSource config, string configName) : base(config, "AssetService") | ||
61 | { | ||
62 | m_log.Debug("[HGAsset Service]: Starting in FSAsset mode"); | ||
63 | IConfig assetConfig = config.Configs[configName]; | ||
64 | if (assetConfig == null) | ||
65 | throw new Exception("No HGAssetService configuration"); | ||
66 | |||
67 | string userAccountsDll = assetConfig.GetString("UserAccountsService", string.Empty); | ||
68 | if (userAccountsDll == string.Empty) | ||
69 | throw new Exception("Please specify UserAccountsService in HGAssetService configuration"); | ||
70 | |||
71 | Object[] args = new Object[] { config }; | ||
72 | m_UserAccountService = ServerUtils.LoadPlugin<IUserAccountService>(userAccountsDll, args); | ||
73 | if (m_UserAccountService == null) | ||
74 | throw new Exception(String.Format("Unable to create UserAccountService from {0}", userAccountsDll)); | ||
75 | |||
76 | m_HomeURL = Util.GetConfigVarFromSections<string>(config, "HomeURI", | ||
77 | new string[] { "Startup", "Hypergrid", configName }, string.Empty); | ||
78 | if (m_HomeURL == string.Empty) | ||
79 | throw new Exception("[HGAssetService] No HomeURI specified"); | ||
80 | |||
81 | m_Cache = UserAccountCache.CreateUserAccountCache(m_UserAccountService); | ||
82 | |||
83 | // Permissions | ||
84 | m_AssetPerms = new AssetPermissions(assetConfig); | ||
85 | } | ||
86 | |||
87 | #region IAssetService overrides | ||
88 | public override AssetBase Get(string id) | ||
89 | { | ||
90 | AssetBase asset = base.Get(id); | ||
91 | |||
92 | if (asset == null) | ||
93 | return null; | ||
94 | |||
95 | if (!m_AssetPerms.AllowedExport(asset.Type)) | ||
96 | return null; | ||
97 | |||
98 | if (asset.Metadata.Type == (sbyte)AssetType.Object) | ||
99 | asset.Data = AdjustIdentifiers(asset.Data); | ||
100 | |||
101 | AdjustIdentifiers(asset.Metadata); | ||
102 | |||
103 | return asset; | ||
104 | } | ||
105 | |||
106 | public override AssetMetadata GetMetadata(string id) | ||
107 | { | ||
108 | AssetMetadata meta = base.GetMetadata(id); | ||
109 | |||
110 | if (meta == null) | ||
111 | return null; | ||
112 | |||
113 | AdjustIdentifiers(meta); | ||
114 | |||
115 | return meta; | ||
116 | } | ||
117 | |||
118 | public override byte[] GetData(string id) | ||
119 | { | ||
120 | AssetBase asset = Get(id); | ||
121 | |||
122 | if (asset == null) | ||
123 | return null; | ||
124 | |||
125 | if (!m_AssetPerms.AllowedExport(asset.Type)) | ||
126 | return null; | ||
127 | |||
128 | // Deal with bug introduced in Oct. 20 (1eb3e6cc43e2a7b4053bc1185c7c88e22356c5e8) | ||
129 | // Fix bad assets before sending them elsewhere | ||
130 | if (asset.Type == (int)AssetType.Object && asset.Data != null) | ||
131 | { | ||
132 | string xml = ExternalRepresentationUtils.SanitizeXml(Utils.BytesToString(asset.Data)); | ||
133 | asset.Data = Utils.StringToBytes(xml); | ||
134 | } | ||
135 | |||
136 | return asset.Data; | ||
137 | } | ||
138 | |||
139 | //public virtual bool Get(string id, Object sender, AssetRetrieved handler) | ||
140 | |||
141 | public override string Store(AssetBase asset) | ||
142 | { | ||
143 | if (!m_AssetPerms.AllowedImport(asset.Type)) | ||
144 | return string.Empty; | ||
145 | |||
146 | // Deal with bug introduced in Oct. 20 (1eb3e6cc43e2a7b4053bc1185c7c88e22356c5e8) | ||
147 | // Fix bad assets before storing on this server | ||
148 | if (asset.Type == (int)AssetType.Object && asset.Data != null) | ||
149 | { | ||
150 | string xml = ExternalRepresentationUtils.SanitizeXml(Utils.BytesToString(asset.Data)); | ||
151 | asset.Data = Utils.StringToBytes(xml); | ||
152 | } | ||
153 | |||
154 | return base.Store(asset); | ||
155 | } | ||
156 | |||
157 | public override bool Delete(string id) | ||
158 | { | ||
159 | // NOGO | ||
160 | return false; | ||
161 | } | ||
162 | |||
163 | #endregion | ||
164 | |||
165 | protected void AdjustIdentifiers(AssetMetadata meta) | ||
166 | { | ||
167 | if (meta == null || m_Cache == null) | ||
168 | return; | ||
169 | |||
170 | UserAccount creator = m_Cache.GetUser(meta.CreatorID); | ||
171 | if (creator != null) | ||
172 | meta.CreatorID = meta.CreatorID + ";" + m_HomeURL + "/" + creator.FirstName + " " + creator.LastName; | ||
173 | } | ||
174 | |||
175 | // Only for Object | ||
176 | protected byte[] AdjustIdentifiers(byte[] data) | ||
177 | { | ||
178 | string xml = Utils.BytesToString(data); | ||
179 | |||
180 | // Deal with bug introduced in Oct. 20 (1eb3e6cc43e2a7b4053bc1185c7c88e22356c5e8) | ||
181 | // Fix bad assets before sending them elsewhere | ||
182 | xml = ExternalRepresentationUtils.SanitizeXml(xml); | ||
183 | |||
184 | return Utils.StringToBytes(ExternalRepresentationUtils.RewriteSOP(xml, "HGAssetService", m_HomeURL, m_Cache, UUID.Zero)); | ||
185 | } | ||
186 | |||
187 | } | ||
188 | |||
189 | } | ||
diff --git a/OpenSim/Services/HypergridService/HGFriendsService.cs b/OpenSim/Services/HypergridService/HGFriendsService.cs new file mode 100644 index 0000000..6e35a88 --- /dev/null +++ b/OpenSim/Services/HypergridService/HGFriendsService.cs | |||
@@ -0,0 +1,409 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Net; | ||
31 | using System.Reflection; | ||
32 | |||
33 | using OpenSim.Framework; | ||
34 | using OpenSim.Services.Connectors.Friends; | ||
35 | using OpenSim.Services.Connectors.Hypergrid; | ||
36 | using OpenSim.Services.Interfaces; | ||
37 | using GridRegion = OpenSim.Services.Interfaces.GridRegion; | ||
38 | using OpenSim.Server.Base; | ||
39 | using FriendInfo = OpenSim.Services.Interfaces.FriendInfo; | ||
40 | |||
41 | using OpenMetaverse; | ||
42 | using log4net; | ||
43 | using Nini.Config; | ||
44 | |||
45 | namespace OpenSim.Services.HypergridService | ||
46 | { | ||
47 | /// <summary> | ||
48 | /// W2W social networking | ||
49 | /// </summary> | ||
50 | public class HGFriendsService : IHGFriendsService | ||
51 | { | ||
52 | private static readonly ILog m_log = | ||
53 | LogManager.GetLogger( | ||
54 | MethodBase.GetCurrentMethod().DeclaringType); | ||
55 | |||
56 | static bool m_Initialized = false; | ||
57 | |||
58 | protected static IGridUserService m_GridUserService; | ||
59 | protected static IGridService m_GridService; | ||
60 | protected static IGatekeeperService m_GatekeeperService; | ||
61 | protected static IFriendsService m_FriendsService; | ||
62 | protected static IPresenceService m_PresenceService; | ||
63 | protected static IUserAccountService m_UserAccountService; | ||
64 | protected static IFriendsSimConnector m_FriendsLocalSimConnector; // standalone, points to HGFriendsModule | ||
65 | protected static FriendsSimConnector m_FriendsSimConnector; // grid | ||
66 | |||
67 | private static string m_ConfigName = "HGFriendsService"; | ||
68 | |||
69 | public HGFriendsService(IConfigSource config, String configName, IFriendsSimConnector localSimConn) | ||
70 | { | ||
71 | if (m_FriendsLocalSimConnector == null) | ||
72 | m_FriendsLocalSimConnector = localSimConn; | ||
73 | |||
74 | if (!m_Initialized) | ||
75 | { | ||
76 | m_Initialized = true; | ||
77 | |||
78 | if (configName != String.Empty) | ||
79 | m_ConfigName = configName; | ||
80 | |||
81 | Object[] args = new Object[] { config }; | ||
82 | |||
83 | IConfig serverConfig = config.Configs[m_ConfigName]; | ||
84 | if (serverConfig == null) | ||
85 | throw new Exception(String.Format("No section {0} in config file", m_ConfigName)); | ||
86 | |||
87 | string theService = serverConfig.GetString("FriendsService", string.Empty); | ||
88 | if (theService == String.Empty) | ||
89 | throw new Exception("No FriendsService in config file " + m_ConfigName); | ||
90 | m_FriendsService = ServerUtils.LoadPlugin<IFriendsService>(theService, args); | ||
91 | |||
92 | theService = serverConfig.GetString("UserAccountService", string.Empty); | ||
93 | if (theService == String.Empty) | ||
94 | throw new Exception("No UserAccountService in " + m_ConfigName); | ||
95 | m_UserAccountService = ServerUtils.LoadPlugin<IUserAccountService>(theService, args); | ||
96 | |||
97 | theService = serverConfig.GetString("GridService", string.Empty); | ||
98 | if (theService == String.Empty) | ||
99 | throw new Exception("No GridService in " + m_ConfigName); | ||
100 | m_GridService = ServerUtils.LoadPlugin<IGridService>(theService, args); | ||
101 | |||
102 | theService = serverConfig.GetString("PresenceService", string.Empty); | ||
103 | if (theService == String.Empty) | ||
104 | throw new Exception("No PresenceService in " + m_ConfigName); | ||
105 | m_PresenceService = ServerUtils.LoadPlugin<IPresenceService>(theService, args); | ||
106 | |||
107 | m_FriendsSimConnector = new FriendsSimConnector(); | ||
108 | |||
109 | m_log.DebugFormat("[HGFRIENDS SERVICE]: Starting..."); | ||
110 | |||
111 | } | ||
112 | } | ||
113 | |||
114 | #region IHGFriendsService | ||
115 | |||
116 | public int GetFriendPerms(UUID userID, UUID friendID) | ||
117 | { | ||
118 | FriendInfo[] friendsInfo = m_FriendsService.GetFriends(userID); | ||
119 | foreach (FriendInfo finfo in friendsInfo) | ||
120 | { | ||
121 | if (finfo.Friend.StartsWith(friendID.ToString())) | ||
122 | return finfo.TheirFlags; | ||
123 | } | ||
124 | return -1; | ||
125 | } | ||
126 | |||
127 | public bool NewFriendship(FriendInfo friend, bool verified) | ||
128 | { | ||
129 | UUID friendID; | ||
130 | string tmp = string.Empty, url = String.Empty, first = String.Empty, last = String.Empty; | ||
131 | if (!Util.ParseUniversalUserIdentifier(friend.Friend, out friendID, out url, out first, out last, out tmp)) | ||
132 | return false; | ||
133 | |||
134 | m_log.DebugFormat("[HGFRIENDS SERVICE]: New friendship {0} {1} ({2})", friend.PrincipalID, friend.Friend, verified); | ||
135 | |||
136 | // Does the friendship already exist? | ||
137 | FriendInfo[] finfos = m_FriendsService.GetFriends(friend.PrincipalID); | ||
138 | foreach (FriendInfo finfo in finfos) | ||
139 | { | ||
140 | if (finfo.Friend.StartsWith(friendID.ToString())) | ||
141 | return false; | ||
142 | } | ||
143 | // Verified user session. But the user needs to confirm friendship when he gets home | ||
144 | if (verified) | ||
145 | return m_FriendsService.StoreFriend(friend.PrincipalID.ToString(), friend.Friend, 0); | ||
146 | |||
147 | // Does the reverted friendship exist? meaning that this user initiated the request | ||
148 | finfos = m_FriendsService.GetFriends(friendID); | ||
149 | bool userInitiatedOffer = false; | ||
150 | foreach (FriendInfo finfo in finfos) | ||
151 | { | ||
152 | if (friend.Friend.StartsWith(finfo.PrincipalID.ToString()) && finfo.Friend.StartsWith(friend.PrincipalID.ToString()) && finfo.TheirFlags == -1) | ||
153 | { | ||
154 | userInitiatedOffer = true; | ||
155 | // Let's delete the existing friendship relations that was stored | ||
156 | m_FriendsService.Delete(friendID, finfo.Friend); | ||
157 | break; | ||
158 | } | ||
159 | } | ||
160 | |||
161 | if (userInitiatedOffer) | ||
162 | { | ||
163 | m_FriendsService.StoreFriend(friend.PrincipalID.ToString(), friend.Friend, 1); | ||
164 | m_FriendsService.StoreFriend(friend.Friend, friend.PrincipalID.ToString(), 1); | ||
165 | // notify the user | ||
166 | ForwardToSim("ApproveFriendshipRequest", friendID, Util.UniversalName(first, last, url), "", friend.PrincipalID, ""); | ||
167 | return true; | ||
168 | } | ||
169 | return false; | ||
170 | } | ||
171 | |||
172 | public bool DeleteFriendship(FriendInfo friend, string secret) | ||
173 | { | ||
174 | FriendInfo[] finfos = m_FriendsService.GetFriends(friend.PrincipalID); | ||
175 | foreach (FriendInfo finfo in finfos) | ||
176 | { | ||
177 | // We check the secret here. Or if the friendship request was initiated here, and was declined | ||
178 | if (finfo.Friend.StartsWith(friend.Friend) && finfo.Friend.EndsWith(secret)) | ||
179 | { | ||
180 | m_log.DebugFormat("[HGFRIENDS SERVICE]: Delete friendship {0} {1}", friend.PrincipalID, friend.Friend); | ||
181 | m_FriendsService.Delete(friend.PrincipalID, finfo.Friend); | ||
182 | m_FriendsService.Delete(finfo.Friend, friend.PrincipalID.ToString()); | ||
183 | |||
184 | return true; | ||
185 | } | ||
186 | } | ||
187 | |||
188 | return false; | ||
189 | } | ||
190 | |||
191 | public bool FriendshipOffered(UUID fromID, string fromName, UUID toID, string message) | ||
192 | { | ||
193 | UserAccount account = m_UserAccountService.GetUserAccount(UUID.Zero, toID); | ||
194 | if (account == null) | ||
195 | return false; | ||
196 | |||
197 | // OK, we have that user here. | ||
198 | // So let's send back the call, but start a thread to continue | ||
199 | // with the verification and the actual action. | ||
200 | |||
201 | Util.FireAndForget( | ||
202 | o => ProcessFriendshipOffered(fromID, fromName, toID, message), null, "HGFriendsService.ProcessFriendshipOffered"); | ||
203 | |||
204 | return true; | ||
205 | } | ||
206 | |||
207 | public bool ValidateFriendshipOffered(UUID fromID, UUID toID) | ||
208 | { | ||
209 | FriendInfo[] finfos = m_FriendsService.GetFriends(toID.ToString()); | ||
210 | foreach (FriendInfo fi in finfos) | ||
211 | { | ||
212 | if (fi.Friend.StartsWith(fromID.ToString()) && fi.TheirFlags == -1) | ||
213 | return true; | ||
214 | } | ||
215 | return false; | ||
216 | } | ||
217 | |||
218 | public List<UUID> StatusNotification(List<string> friends, UUID foreignUserID, bool online) | ||
219 | { | ||
220 | if (m_FriendsService == null || m_PresenceService == null) | ||
221 | { | ||
222 | m_log.WarnFormat("[HGFRIENDS SERVICE]: Unable to perform status notifications because friends or presence services are missing"); | ||
223 | return new List<UUID>(); | ||
224 | } | ||
225 | |||
226 | // Let's unblock the caller right now, and take it from here async | ||
227 | |||
228 | List<UUID> localFriendsOnline = new List<UUID>(); | ||
229 | |||
230 | m_log.DebugFormat("[HGFRIENDS SERVICE]: Status notification: foreign user {0} wants to notify {1} local friends of {2} status", | ||
231 | foreignUserID, friends.Count, (online ? "online" : "offline")); | ||
232 | |||
233 | // First, let's double check that the reported friends are, indeed, friends of that user | ||
234 | // And let's check that the secret matches | ||
235 | List<string> usersToBeNotified = new List<string>(); | ||
236 | foreach (string uui in friends) | ||
237 | { | ||
238 | UUID localUserID; | ||
239 | string secret = string.Empty, tmp = string.Empty; | ||
240 | if (Util.ParseUniversalUserIdentifier(uui, out localUserID, out tmp, out tmp, out tmp, out secret)) | ||
241 | { | ||
242 | FriendInfo[] friendInfos = m_FriendsService.GetFriends(localUserID); | ||
243 | foreach (FriendInfo finfo in friendInfos) | ||
244 | { | ||
245 | if (finfo.Friend.StartsWith(foreignUserID.ToString()) && finfo.Friend.EndsWith(secret)) | ||
246 | { | ||
247 | // great! | ||
248 | usersToBeNotified.Add(localUserID.ToString()); | ||
249 | } | ||
250 | } | ||
251 | } | ||
252 | } | ||
253 | |||
254 | // Now, let's send the notifications | ||
255 | //m_log.DebugFormat("[HGFRIENDS SERVICE]: Status notification: user has {0} local friends", usersToBeNotified.Count); | ||
256 | |||
257 | // First, let's send notifications to local users who are online in the home grid | ||
258 | PresenceInfo[] friendSessions = m_PresenceService.GetAgents(usersToBeNotified.ToArray()); | ||
259 | if (friendSessions != null && friendSessions.Length > 0) | ||
260 | { | ||
261 | PresenceInfo friendSession = null; | ||
262 | foreach (PresenceInfo pinfo in friendSessions) | ||
263 | if (pinfo.RegionID != UUID.Zero) // let's guard against traveling agents | ||
264 | { | ||
265 | friendSession = pinfo; | ||
266 | break; | ||
267 | } | ||
268 | |||
269 | if (friendSession != null) | ||
270 | { | ||
271 | ForwardStatusNotificationToSim(friendSession.RegionID, foreignUserID, friendSession.UserID, online); | ||
272 | usersToBeNotified.Remove(friendSession.UserID.ToString()); | ||
273 | UUID id; | ||
274 | if (UUID.TryParse(friendSession.UserID, out id)) | ||
275 | localFriendsOnline.Add(id); | ||
276 | |||
277 | } | ||
278 | } | ||
279 | |||
280 | // // Lastly, let's notify the rest who may be online somewhere else | ||
281 | // foreach (string user in usersToBeNotified) | ||
282 | // { | ||
283 | // UUID id = new UUID(user); | ||
284 | // //m_UserAgentService.LocateUser(id); | ||
285 | // //etc... | ||
286 | // //if (m_TravelingAgents.ContainsKey(id) && m_TravelingAgents[id].GridExternalName != m_GridName) | ||
287 | // //{ | ||
288 | // // string url = m_TravelingAgents[id].GridExternalName; | ||
289 | // // // forward | ||
290 | // //} | ||
291 | // //m_log.WarnFormat("[HGFRIENDS SERVICE]: User {0} is visiting another grid. HG Status notifications still not implemented.", user); | ||
292 | // } | ||
293 | |||
294 | // and finally, let's send the online friends | ||
295 | if (online) | ||
296 | { | ||
297 | return localFriendsOnline; | ||
298 | } | ||
299 | else | ||
300 | return new List<UUID>(); | ||
301 | } | ||
302 | |||
303 | #endregion IHGFriendsService | ||
304 | |||
305 | #region Aux | ||
306 | |||
307 | private void ProcessFriendshipOffered(UUID fromID, String fromName, UUID toID, String message) | ||
308 | { | ||
309 | // Great, it's a genuine request. Let's proceed. | ||
310 | // But now we need to confirm that the requester is who he says he is | ||
311 | // before we act on the friendship request. | ||
312 | |||
313 | if (!fromName.Contains("@")) | ||
314 | return; | ||
315 | |||
316 | string[] parts = fromName.Split(new char[] {'@'}); | ||
317 | if (parts.Length != 2) | ||
318 | return; | ||
319 | |||
320 | string uriStr = "http://" + parts[1]; | ||
321 | try | ||
322 | { | ||
323 | new Uri(uriStr); | ||
324 | } | ||
325 | catch (UriFormatException) | ||
326 | { | ||
327 | return; | ||
328 | } | ||
329 | |||
330 | UserAgentServiceConnector uasConn = new UserAgentServiceConnector(uriStr); | ||
331 | Dictionary<string, object> servers = uasConn.GetServerURLs(fromID); | ||
332 | if (!servers.ContainsKey("FriendsServerURI")) | ||
333 | return; | ||
334 | |||
335 | HGFriendsServicesConnector friendsConn = new HGFriendsServicesConnector(servers["FriendsServerURI"].ToString()); | ||
336 | if (!friendsConn.ValidateFriendshipOffered(fromID, toID)) | ||
337 | { | ||
338 | m_log.WarnFormat("[HGFRIENDS SERVICE]: Friendship request from {0} to {1} is invalid. Impersonations?", fromID, toID); | ||
339 | return; | ||
340 | } | ||
341 | |||
342 | string fromUUI = Util.UniversalIdentifier(fromID, parts[0], "@" + parts[1], uriStr); | ||
343 | // OK, we're good! | ||
344 | ForwardToSim("FriendshipOffered", fromID, fromName, fromUUI, toID, message); | ||
345 | } | ||
346 | |||
347 | private bool ForwardToSim(string op, UUID fromID, string name, String fromUUI, UUID toID, string message) | ||
348 | { | ||
349 | PresenceInfo session = null; | ||
350 | GridRegion region = null; | ||
351 | PresenceInfo[] sessions = m_PresenceService.GetAgents(new string[] { toID.ToString() }); | ||
352 | if (sessions != null && sessions.Length > 0) | ||
353 | session = sessions[0]; | ||
354 | if (session != null) | ||
355 | region = m_GridService.GetRegionByUUID(UUID.Zero, session.RegionID); | ||
356 | |||
357 | switch (op) | ||
358 | { | ||
359 | case "FriendshipOffered": | ||
360 | // Let's store backwards | ||
361 | string secret = UUID.Random().ToString().Substring(0, 8); | ||
362 | m_FriendsService.StoreFriend(toID.ToString(), fromUUI + ";" + secret, 0); | ||
363 | if (m_FriendsLocalSimConnector != null) // standalone | ||
364 | { | ||
365 | GridInstantMessage im = new GridInstantMessage(null, fromID, name, toID, | ||
366 | (byte)InstantMessageDialog.FriendshipOffered, message, false, Vector3.Zero); | ||
367 | // !! HACK | ||
368 | im.imSessionID = im.fromAgentID; | ||
369 | return m_FriendsLocalSimConnector.LocalFriendshipOffered(toID, im); | ||
370 | } | ||
371 | else if (region != null) // grid | ||
372 | return m_FriendsSimConnector.FriendshipOffered(region, fromID, toID, message, name); | ||
373 | break; | ||
374 | case "ApproveFriendshipRequest": | ||
375 | if (m_FriendsLocalSimConnector != null) // standalone | ||
376 | return m_FriendsLocalSimConnector.LocalFriendshipApproved(fromID, name, toID); | ||
377 | else if (region != null) //grid | ||
378 | return m_FriendsSimConnector.FriendshipApproved(region, fromID, name, toID); | ||
379 | break; | ||
380 | } | ||
381 | |||
382 | return false; | ||
383 | } | ||
384 | |||
385 | protected void ForwardStatusNotificationToSim(UUID regionID, UUID foreignUserID, string user, bool online) | ||
386 | { | ||
387 | UUID userID; | ||
388 | if (UUID.TryParse(user, out userID)) | ||
389 | { | ||
390 | if (m_FriendsLocalSimConnector != null) | ||
391 | { | ||
392 | m_log.DebugFormat("[HGFRIENDS SERVICE]: Local Notify, user {0} is {1}", foreignUserID, (online ? "online" : "offline")); | ||
393 | m_FriendsLocalSimConnector.StatusNotify(foreignUserID, userID, online); | ||
394 | } | ||
395 | else | ||
396 | { | ||
397 | GridRegion region = m_GridService.GetRegionByUUID(UUID.Zero /* !!! */, regionID); | ||
398 | if (region != null) | ||
399 | { | ||
400 | m_log.DebugFormat("[HGFRIENDS SERVICE]: Remote Notify to region {0}, user {1} is {2}", region.RegionName, foreignUserID, (online ? "online" : "offline")); | ||
401 | m_FriendsSimConnector.StatusNotify(region, foreignUserID, userID.ToString(), online); | ||
402 | } | ||
403 | } | ||
404 | } | ||
405 | } | ||
406 | |||
407 | #endregion Aux | ||
408 | } | ||
409 | } | ||
diff --git a/OpenSim/Services/HypergridService/HGInstantMessageService.cs b/OpenSim/Services/HypergridService/HGInstantMessageService.cs new file mode 100644 index 0000000..32ca09a --- /dev/null +++ b/OpenSim/Services/HypergridService/HGInstantMessageService.cs | |||
@@ -0,0 +1,376 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Net; | ||
31 | using System.Reflection; | ||
32 | |||
33 | using OpenSim.Framework; | ||
34 | using OpenSim.Services.Connectors.Friends; | ||
35 | using OpenSim.Services.Connectors.Hypergrid; | ||
36 | using OpenSim.Services.Interfaces; | ||
37 | using OpenSim.Services.Connectors.InstantMessage; | ||
38 | using GridRegion = OpenSim.Services.Interfaces.GridRegion; | ||
39 | using OpenSim.Server.Base; | ||
40 | using FriendInfo = OpenSim.Services.Interfaces.FriendInfo; | ||
41 | |||
42 | using OpenMetaverse; | ||
43 | using log4net; | ||
44 | using Nini.Config; | ||
45 | |||
46 | namespace OpenSim.Services.HypergridService | ||
47 | { | ||
48 | /// <summary> | ||
49 | /// Inter-grid IM | ||
50 | /// </summary> | ||
51 | public class HGInstantMessageService : IInstantMessage | ||
52 | { | ||
53 | private static readonly ILog m_log = | ||
54 | LogManager.GetLogger( | ||
55 | MethodBase.GetCurrentMethod().DeclaringType); | ||
56 | |||
57 | private const double CACHE_EXPIRATION_SECONDS = 120000.0; // 33 hours | ||
58 | |||
59 | static bool m_Initialized = false; | ||
60 | |||
61 | protected static IGridService m_GridService; | ||
62 | protected static IPresenceService m_PresenceService; | ||
63 | protected static IUserAgentService m_UserAgentService; | ||
64 | protected static IOfflineIMService m_OfflineIMService; | ||
65 | |||
66 | protected static IInstantMessageSimConnector m_IMSimConnector; | ||
67 | |||
68 | protected static Dictionary<UUID, object> m_UserLocationMap = new Dictionary<UUID, object>(); | ||
69 | private static ExpiringCache<UUID, GridRegion> m_RegionCache; | ||
70 | |||
71 | private static bool m_ForwardOfflineGroupMessages; | ||
72 | private static bool m_InGatekeeper; | ||
73 | |||
74 | public HGInstantMessageService(IConfigSource config) | ||
75 | : this(config, null) | ||
76 | { | ||
77 | } | ||
78 | |||
79 | public HGInstantMessageService(IConfigSource config, IInstantMessageSimConnector imConnector) | ||
80 | { | ||
81 | if (imConnector != null) | ||
82 | m_IMSimConnector = imConnector; | ||
83 | |||
84 | if (!m_Initialized) | ||
85 | { | ||
86 | m_Initialized = true; | ||
87 | |||
88 | IConfig serverConfig = config.Configs["HGInstantMessageService"]; | ||
89 | if (serverConfig == null) | ||
90 | throw new Exception(String.Format("No section HGInstantMessageService in config file")); | ||
91 | |||
92 | string gridService = serverConfig.GetString("GridService", String.Empty); | ||
93 | string presenceService = serverConfig.GetString("PresenceService", String.Empty); | ||
94 | string userAgentService = serverConfig.GetString("UserAgentService", String.Empty); | ||
95 | m_InGatekeeper = serverConfig.GetBoolean("InGatekeeper", false); | ||
96 | m_log.DebugFormat("[HG IM SERVICE]: Starting... InRobust? {0}", m_InGatekeeper); | ||
97 | |||
98 | if (gridService == string.Empty || presenceService == string.Empty) | ||
99 | throw new Exception(String.Format("Incomplete specifications, InstantMessage Service cannot function.")); | ||
100 | |||
101 | Object[] args = new Object[] { config }; | ||
102 | m_GridService = ServerUtils.LoadPlugin<IGridService>(gridService, args); | ||
103 | m_PresenceService = ServerUtils.LoadPlugin<IPresenceService>(presenceService, args); | ||
104 | try | ||
105 | { | ||
106 | m_UserAgentService = ServerUtils.LoadPlugin<IUserAgentService>(userAgentService, args); | ||
107 | } | ||
108 | catch | ||
109 | { | ||
110 | m_log.WarnFormat("[HG IM SERVICE]: Unable to create User Agent Service. Missing config var in [HGInstantMessageService]?"); | ||
111 | } | ||
112 | |||
113 | m_RegionCache = new ExpiringCache<UUID, GridRegion>(); | ||
114 | |||
115 | IConfig cnf = config.Configs["Messaging"]; | ||
116 | if (cnf == null) | ||
117 | { | ||
118 | return; | ||
119 | } | ||
120 | |||
121 | m_ForwardOfflineGroupMessages = cnf.GetBoolean("ForwardOfflineGroupMessages", false); | ||
122 | |||
123 | if (m_InGatekeeper) | ||
124 | { | ||
125 | string offlineIMService = cnf.GetString("OfflineIMService", string.Empty); | ||
126 | if (offlineIMService != string.Empty) | ||
127 | m_OfflineIMService = ServerUtils.LoadPlugin<IOfflineIMService>(offlineIMService, args); | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | |||
132 | public bool IncomingInstantMessage(GridInstantMessage im) | ||
133 | { | ||
134 | // m_log.DebugFormat("[HG IM SERVICE]: Received message from {0} to {1}", im.fromAgentID, im.toAgentID); | ||
135 | // UUID toAgentID = new UUID(im.toAgentID); | ||
136 | |||
137 | bool success = false; | ||
138 | if (m_IMSimConnector != null) | ||
139 | { | ||
140 | //m_log.DebugFormat("[XXX] SendIMToRegion local im connector"); | ||
141 | success = m_IMSimConnector.SendInstantMessage(im); | ||
142 | } | ||
143 | else | ||
144 | { | ||
145 | success = TrySendInstantMessage(im, "", true, false); | ||
146 | } | ||
147 | |||
148 | if (!success && m_InGatekeeper) // we do this only in the Gatekeeper IM service | ||
149 | UndeliveredMessage(im); | ||
150 | |||
151 | return success; | ||
152 | } | ||
153 | |||
154 | public bool OutgoingInstantMessage(GridInstantMessage im, string url, bool foreigner) | ||
155 | { | ||
156 | // m_log.DebugFormat("[HG IM SERVICE]: Sending message from {0} to {1}@{2}", im.fromAgentID, im.toAgentID, url); | ||
157 | if (url != string.Empty) | ||
158 | return TrySendInstantMessage(im, url, true, foreigner); | ||
159 | else | ||
160 | { | ||
161 | PresenceInfo upd = new PresenceInfo(); | ||
162 | upd.RegionID = UUID.Zero; | ||
163 | return TrySendInstantMessage(im, upd, true, foreigner); | ||
164 | } | ||
165 | |||
166 | } | ||
167 | |||
168 | protected bool TrySendInstantMessage(GridInstantMessage im, object previousLocation, bool firstTime, bool foreigner) | ||
169 | { | ||
170 | UUID toAgentID = new UUID(im.toAgentID); | ||
171 | |||
172 | PresenceInfo upd = null; | ||
173 | string url = string.Empty; | ||
174 | |||
175 | bool lookupAgent = false; | ||
176 | |||
177 | lock (m_UserLocationMap) | ||
178 | { | ||
179 | if (m_UserLocationMap.ContainsKey(toAgentID)) | ||
180 | { | ||
181 | object o = m_UserLocationMap[toAgentID]; | ||
182 | if (o is PresenceInfo) | ||
183 | upd = (PresenceInfo)o; | ||
184 | else if (o is string) | ||
185 | url = (string)o; | ||
186 | |||
187 | // We need to compare the current location with the previous | ||
188 | // or the recursive loop will never end because it will never try to lookup the agent again | ||
189 | if (!firstTime) | ||
190 | { | ||
191 | lookupAgent = true; | ||
192 | upd = null; | ||
193 | } | ||
194 | } | ||
195 | else | ||
196 | { | ||
197 | lookupAgent = true; | ||
198 | } | ||
199 | } | ||
200 | |||
201 | //m_log.DebugFormat("[XXX] Neeed lookup ? {0}", (lookupAgent ? "yes" : "no")); | ||
202 | |||
203 | // Are we needing to look-up an agent? | ||
204 | if (lookupAgent) | ||
205 | { | ||
206 | // Non-cached user agent lookup. | ||
207 | PresenceInfo[] presences = m_PresenceService.GetAgents(new string[] { toAgentID.ToString() }); | ||
208 | if (presences != null && presences.Length > 0) | ||
209 | { | ||
210 | foreach (PresenceInfo p in presences) | ||
211 | { | ||
212 | if (p.RegionID != UUID.Zero) | ||
213 | { | ||
214 | //m_log.DebugFormat("[XXX]: Found presence in {0}", p.RegionID); | ||
215 | upd = p; | ||
216 | break; | ||
217 | } | ||
218 | } | ||
219 | } | ||
220 | |||
221 | if (upd == null && !foreigner) | ||
222 | { | ||
223 | // Let's check with the UAS if the user is elsewhere | ||
224 | m_log.DebugFormat("[HG IM SERVICE]: User is not present. Checking location with User Agent service"); | ||
225 | try | ||
226 | { | ||
227 | url = m_UserAgentService.LocateUser(toAgentID); | ||
228 | } | ||
229 | catch (Exception e) | ||
230 | { | ||
231 | m_log.Warn("[HG IM SERVICE]: LocateUser call failed ", e); | ||
232 | url = string.Empty; | ||
233 | } | ||
234 | } | ||
235 | |||
236 | // check if we've tried this before.. | ||
237 | // This is one way to end the recursive loop | ||
238 | // | ||
239 | if (!firstTime && ((previousLocation is PresenceInfo && upd != null && upd.RegionID == ((PresenceInfo)previousLocation).RegionID) || | ||
240 | (previousLocation is string && upd == null && previousLocation.Equals(url)))) | ||
241 | { | ||
242 | // m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message"); | ||
243 | m_log.DebugFormat("[HG IM SERVICE]: Fail 2 {0} {1}", previousLocation, url); | ||
244 | |||
245 | return false; | ||
246 | } | ||
247 | } | ||
248 | |||
249 | if (upd != null) | ||
250 | { | ||
251 | // ok, the user is around somewhere. Let's send back the reply with "success" | ||
252 | // even though the IM may still fail. Just don't keep the caller waiting for | ||
253 | // the entire time we're trying to deliver the IM | ||
254 | return SendIMToRegion(upd, im, toAgentID, foreigner); | ||
255 | } | ||
256 | else if (url != string.Empty) | ||
257 | { | ||
258 | // ok, the user is around somewhere. Let's send back the reply with "success" | ||
259 | // even though the IM may still fail. Just don't keep the caller waiting for | ||
260 | // the entire time we're trying to deliver the IM | ||
261 | return ForwardIMToGrid(url, im, toAgentID, foreigner); | ||
262 | } | ||
263 | else if (firstTime && previousLocation is string && (string)previousLocation != string.Empty) | ||
264 | { | ||
265 | return ForwardIMToGrid((string)previousLocation, im, toAgentID, foreigner); | ||
266 | } | ||
267 | else | ||
268 | m_log.DebugFormat("[HG IM SERVICE]: Unable to locate user {0}", toAgentID); | ||
269 | return false; | ||
270 | } | ||
271 | |||
272 | bool SendIMToRegion(PresenceInfo upd, GridInstantMessage im, UUID toAgentID, bool foreigner) | ||
273 | { | ||
274 | bool imresult = false; | ||
275 | GridRegion reginfo = null; | ||
276 | if (!m_RegionCache.TryGetValue(upd.RegionID, out reginfo)) | ||
277 | { | ||
278 | reginfo = m_GridService.GetRegionByUUID(UUID.Zero /*!!!*/, upd.RegionID); | ||
279 | if (reginfo != null) | ||
280 | m_RegionCache.AddOrUpdate(upd.RegionID, reginfo, CACHE_EXPIRATION_SECONDS); | ||
281 | } | ||
282 | |||
283 | if (reginfo != null) | ||
284 | { | ||
285 | imresult = InstantMessageServiceConnector.SendInstantMessage(reginfo.ServerURI, im); | ||
286 | } | ||
287 | else | ||
288 | { | ||
289 | m_log.DebugFormat("[HG IM SERVICE]: Failed to deliver message to {0}", reginfo.ServerURI); | ||
290 | return false; | ||
291 | } | ||
292 | |||
293 | if (imresult) | ||
294 | { | ||
295 | // IM delivery successful, so store the Agent's location in our local cache. | ||
296 | lock (m_UserLocationMap) | ||
297 | { | ||
298 | if (m_UserLocationMap.ContainsKey(toAgentID)) | ||
299 | { | ||
300 | m_UserLocationMap[toAgentID] = upd; | ||
301 | } | ||
302 | else | ||
303 | { | ||
304 | m_UserLocationMap.Add(toAgentID, upd); | ||
305 | } | ||
306 | } | ||
307 | return true; | ||
308 | } | ||
309 | else | ||
310 | { | ||
311 | // try again, but lookup user this time. | ||
312 | // Warning, this must call the Async version | ||
313 | // of this method or we'll be making thousands of threads | ||
314 | // The version within the spawned thread is SendGridInstantMessageViaXMLRPCAsync | ||
315 | // The version that spawns the thread is SendGridInstantMessageViaXMLRPC | ||
316 | |||
317 | // This is recursive!!!!! | ||
318 | return TrySendInstantMessage(im, upd, false, foreigner); | ||
319 | } | ||
320 | } | ||
321 | |||
322 | bool ForwardIMToGrid(string url, GridInstantMessage im, UUID toAgentID, bool foreigner) | ||
323 | { | ||
324 | if (InstantMessageServiceConnector.SendInstantMessage(url, im)) | ||
325 | { | ||
326 | // IM delivery successful, so store the Agent's location in our local cache. | ||
327 | lock (m_UserLocationMap) | ||
328 | { | ||
329 | if (m_UserLocationMap.ContainsKey(toAgentID)) | ||
330 | { | ||
331 | m_UserLocationMap[toAgentID] = url; | ||
332 | } | ||
333 | else | ||
334 | { | ||
335 | m_UserLocationMap.Add(toAgentID, url); | ||
336 | } | ||
337 | } | ||
338 | |||
339 | return true; | ||
340 | } | ||
341 | else | ||
342 | { | ||
343 | // try again, but lookup user this time. | ||
344 | |||
345 | // This is recursive!!!!! | ||
346 | return TrySendInstantMessage(im, url, false, foreigner); | ||
347 | } | ||
348 | } | ||
349 | |||
350 | private bool UndeliveredMessage(GridInstantMessage im) | ||
351 | { | ||
352 | if (m_OfflineIMService == null) | ||
353 | return false; | ||
354 | |||
355 | if (im.dialog != (byte)InstantMessageDialog.MessageFromObject && | ||
356 | im.dialog != (byte)InstantMessageDialog.MessageFromAgent && | ||
357 | im.dialog != (byte)InstantMessageDialog.GroupNotice && | ||
358 | im.dialog != (byte)InstantMessageDialog.GroupInvitation && | ||
359 | im.dialog != (byte)InstantMessageDialog.InventoryOffered) | ||
360 | { | ||
361 | return false; | ||
362 | } | ||
363 | |||
364 | if (!m_ForwardOfflineGroupMessages) | ||
365 | { | ||
366 | if (im.dialog == (byte)InstantMessageDialog.GroupNotice || | ||
367 | im.dialog == (byte)InstantMessageDialog.GroupInvitation) | ||
368 | return false; | ||
369 | } | ||
370 | |||
371 | // m_log.DebugFormat("[HG IM SERVICE]: Message saved"); | ||
372 | string reason = string.Empty; | ||
373 | return m_OfflineIMService.StoreMessage(im, out reason); | ||
374 | } | ||
375 | } | ||
376 | } \ No newline at end of file | ||
diff --git a/OpenSim/Services/HypergridService/HGInventoryService.cs b/OpenSim/Services/HypergridService/HGInventoryService.cs new file mode 100644 index 0000000..9158b41 --- /dev/null +++ b/OpenSim/Services/HypergridService/HGInventoryService.cs | |||
@@ -0,0 +1,321 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using OpenMetaverse; | ||
31 | using log4net; | ||
32 | using Nini.Config; | ||
33 | using System.Reflection; | ||
34 | using OpenSim.Services.Base; | ||
35 | using OpenSim.Services.Interfaces; | ||
36 | using OpenSim.Services.InventoryService; | ||
37 | using OpenSim.Data; | ||
38 | using OpenSim.Framework; | ||
39 | using OpenSim.Server.Base; | ||
40 | |||
41 | namespace OpenSim.Services.HypergridService | ||
42 | { | ||
43 | /// <summary> | ||
44 | /// Hypergrid inventory service. It serves the IInventoryService interface, | ||
45 | /// but implements it in ways that are appropriate for inter-grid | ||
46 | /// inventory exchanges. Specifically, it does not performs deletions | ||
47 | /// and it responds to GetRootFolder requests with the ID of the | ||
48 | /// Suitcase folder, not the actual "My Inventory" folder. | ||
49 | /// </summary> | ||
50 | public class HGInventoryService : XInventoryService, IInventoryService | ||
51 | { | ||
52 | private static readonly ILog m_log = | ||
53 | LogManager.GetLogger( | ||
54 | MethodBase.GetCurrentMethod().DeclaringType); | ||
55 | |||
56 | private string m_HomeURL; | ||
57 | private IUserAccountService m_UserAccountService; | ||
58 | |||
59 | private UserAccountCache m_Cache; | ||
60 | |||
61 | public HGInventoryService(IConfigSource config, string configName) | ||
62 | : base(config, configName) | ||
63 | { | ||
64 | m_log.Debug("[HGInventory Service]: Starting"); | ||
65 | if (configName != string.Empty) | ||
66 | m_ConfigName = configName; | ||
67 | |||
68 | // | ||
69 | // Try reading the [InventoryService] section, if it exists | ||
70 | // | ||
71 | IConfig invConfig = config.Configs[m_ConfigName]; | ||
72 | if (invConfig != null) | ||
73 | { | ||
74 | // realm = authConfig.GetString("Realm", realm); | ||
75 | string userAccountsDll = invConfig.GetString("UserAccountsService", string.Empty); | ||
76 | if (userAccountsDll == string.Empty) | ||
77 | throw new Exception("Please specify UserAccountsService in HGInventoryService configuration"); | ||
78 | |||
79 | Object[] args = new Object[] { config }; | ||
80 | m_UserAccountService = ServerUtils.LoadPlugin<IUserAccountService>(userAccountsDll, args); | ||
81 | if (m_UserAccountService == null) | ||
82 | throw new Exception(String.Format("Unable to create UserAccountService from {0}", userAccountsDll)); | ||
83 | |||
84 | m_HomeURL = Util.GetConfigVarFromSections<string>(config, "HomeURI", | ||
85 | new string[] { "Startup", "Hypergrid", m_ConfigName }, String.Empty); | ||
86 | |||
87 | m_Cache = UserAccountCache.CreateUserAccountCache(m_UserAccountService); | ||
88 | } | ||
89 | |||
90 | m_log.Debug("[HG INVENTORY SERVICE]: Starting..."); | ||
91 | } | ||
92 | |||
93 | public override bool CreateUserInventory(UUID principalID) | ||
94 | { | ||
95 | // NOGO | ||
96 | return false; | ||
97 | } | ||
98 | |||
99 | |||
100 | public override List<InventoryFolderBase> GetInventorySkeleton(UUID principalID) | ||
101 | { | ||
102 | // NOGO for this inventory service | ||
103 | return new List<InventoryFolderBase>(); | ||
104 | } | ||
105 | |||
106 | public override InventoryFolderBase GetRootFolder(UUID principalID) | ||
107 | { | ||
108 | //m_log.DebugFormat("[HG INVENTORY SERVICE]: GetRootFolder for {0}", principalID); | ||
109 | // Warp! Root folder for travelers | ||
110 | XInventoryFolder[] folders = m_Database.GetFolders( | ||
111 | new string[] { "agentID", "folderName"}, | ||
112 | new string[] { principalID.ToString(), "My Suitcase" }); | ||
113 | |||
114 | if (folders.Length > 0) | ||
115 | return ConvertToOpenSim(folders[0]); | ||
116 | |||
117 | // make one | ||
118 | XInventoryFolder suitcase = CreateFolder(principalID, UUID.Zero, (int)FolderType.Suitcase, "My Suitcase"); | ||
119 | return ConvertToOpenSim(suitcase); | ||
120 | } | ||
121 | |||
122 | //private bool CreateSystemFolders(UUID principalID, XInventoryFolder suitcase) | ||
123 | //{ | ||
124 | |||
125 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.Animation, "Animations"); | ||
126 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.Bodypart, "Body Parts"); | ||
127 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.CallingCard, "Calling Cards"); | ||
128 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.Clothing, "Clothing"); | ||
129 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.Gesture, "Gestures"); | ||
130 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.Landmark, "Landmarks"); | ||
131 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.LostAndFoundFolder, "Lost And Found"); | ||
132 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.Notecard, "Notecards"); | ||
133 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.Object, "Objects"); | ||
134 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.SnapshotFolder, "Photo Album"); | ||
135 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.LSLText, "Scripts"); | ||
136 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.Sound, "Sounds"); | ||
137 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.Texture, "Textures"); | ||
138 | // CreateFolder(principalID, suitcase.folderID, (int)AssetType.TrashFolder, "Trash"); | ||
139 | |||
140 | // return true; | ||
141 | //} | ||
142 | |||
143 | |||
144 | public override InventoryFolderBase GetFolderForType(UUID principalID, FolderType type) | ||
145 | { | ||
146 | //m_log.DebugFormat("[HG INVENTORY SERVICE]: GetFolderForType for {0} {0}", principalID, type); | ||
147 | return GetRootFolder(principalID); | ||
148 | } | ||
149 | |||
150 | // | ||
151 | // Use the inherited methods | ||
152 | // | ||
153 | //public InventoryCollection GetFolderContent(UUID principalID, UUID folderID) | ||
154 | //{ | ||
155 | //} | ||
156 | |||
157 | // NOGO | ||
158 | // | ||
159 | public override InventoryCollection[] GetMultipleFoldersContent(UUID principalID, UUID[] folderID) | ||
160 | { | ||
161 | return new InventoryCollection[0]; | ||
162 | } | ||
163 | |||
164 | //public List<InventoryItemBase> GetFolderItems(UUID principalID, UUID folderID) | ||
165 | //{ | ||
166 | //} | ||
167 | |||
168 | //public override bool AddFolder(InventoryFolderBase folder) | ||
169 | //{ | ||
170 | // // Check if it's under the Suitcase folder | ||
171 | // List<InventoryFolderBase> skel = base.GetInventorySkeleton(folder.Owner); | ||
172 | // InventoryFolderBase suitcase = GetRootFolder(folder.Owner); | ||
173 | // List<InventoryFolderBase> suitDescendents = GetDescendents(skel, suitcase.ID); | ||
174 | |||
175 | // foreach (InventoryFolderBase f in suitDescendents) | ||
176 | // if (folder.ParentID == f.ID) | ||
177 | // { | ||
178 | // XInventoryFolder xFolder = ConvertFromOpenSim(folder); | ||
179 | // return m_Database.StoreFolder(xFolder); | ||
180 | // } | ||
181 | // return false; | ||
182 | //} | ||
183 | |||
184 | private List<InventoryFolderBase> GetDescendents(List<InventoryFolderBase> lst, UUID root) | ||
185 | { | ||
186 | List<InventoryFolderBase> direct = lst.FindAll(delegate(InventoryFolderBase f) { return f.ParentID == root; }); | ||
187 | if (direct == null) | ||
188 | return new List<InventoryFolderBase>(); | ||
189 | |||
190 | List<InventoryFolderBase> indirect = new List<InventoryFolderBase>(); | ||
191 | foreach (InventoryFolderBase f in direct) | ||
192 | indirect.AddRange(GetDescendents(lst, f.ID)); | ||
193 | |||
194 | direct.AddRange(indirect); | ||
195 | return direct; | ||
196 | } | ||
197 | |||
198 | // Use inherited method | ||
199 | //public bool UpdateFolder(InventoryFolderBase folder) | ||
200 | //{ | ||
201 | //} | ||
202 | |||
203 | //public override bool MoveFolder(InventoryFolderBase folder) | ||
204 | //{ | ||
205 | // XInventoryFolder[] x = m_Database.GetFolders( | ||
206 | // new string[] { "folderID" }, | ||
207 | // new string[] { folder.ID.ToString() }); | ||
208 | |||
209 | // if (x.Length == 0) | ||
210 | // return false; | ||
211 | |||
212 | // // Check if it's under the Suitcase folder | ||
213 | // List<InventoryFolderBase> skel = base.GetInventorySkeleton(folder.Owner); | ||
214 | // InventoryFolderBase suitcase = GetRootFolder(folder.Owner); | ||
215 | // List<InventoryFolderBase> suitDescendents = GetDescendents(skel, suitcase.ID); | ||
216 | |||
217 | // foreach (InventoryFolderBase f in suitDescendents) | ||
218 | // if (folder.ParentID == f.ID) | ||
219 | // { | ||
220 | // x[0].parentFolderID = folder.ParentID; | ||
221 | // return m_Database.StoreFolder(x[0]); | ||
222 | // } | ||
223 | |||
224 | // return false; | ||
225 | //} | ||
226 | |||
227 | public override bool DeleteFolders(UUID principalID, List<UUID> folderIDs) | ||
228 | { | ||
229 | // NOGO | ||
230 | return false; | ||
231 | } | ||
232 | |||
233 | public override bool PurgeFolder(InventoryFolderBase folder) | ||
234 | { | ||
235 | // NOGO | ||
236 | return false; | ||
237 | } | ||
238 | |||
239 | // Unfortunately we need to use the inherited method because of how DeRez works. | ||
240 | // The viewer sends the folderID hard-wired in the derez message | ||
241 | //public override bool AddItem(InventoryItemBase item) | ||
242 | //{ | ||
243 | // // Check if it's under the Suitcase folder | ||
244 | // List<InventoryFolderBase> skel = base.GetInventorySkeleton(item.Owner); | ||
245 | // InventoryFolderBase suitcase = GetRootFolder(item.Owner); | ||
246 | // List<InventoryFolderBase> suitDescendents = GetDescendents(skel, suitcase.ID); | ||
247 | |||
248 | // foreach (InventoryFolderBase f in suitDescendents) | ||
249 | // if (item.Folder == f.ID) | ||
250 | // return m_Database.StoreItem(ConvertFromOpenSim(item)); | ||
251 | |||
252 | // return false; | ||
253 | //} | ||
254 | |||
255 | //public override bool UpdateItem(InventoryItemBase item) | ||
256 | //{ | ||
257 | // // Check if it's under the Suitcase folder | ||
258 | // List<InventoryFolderBase> skel = base.GetInventorySkeleton(item.Owner); | ||
259 | // InventoryFolderBase suitcase = GetRootFolder(item.Owner); | ||
260 | // List<InventoryFolderBase> suitDescendents = GetDescendents(skel, suitcase.ID); | ||
261 | |||
262 | // foreach (InventoryFolderBase f in suitDescendents) | ||
263 | // if (item.Folder == f.ID) | ||
264 | // return m_Database.StoreItem(ConvertFromOpenSim(item)); | ||
265 | |||
266 | // return false; | ||
267 | //} | ||
268 | |||
269 | //public override bool MoveItems(UUID principalID, List<InventoryItemBase> items) | ||
270 | //{ | ||
271 | // // Principal is b0rked. *sigh* | ||
272 | // // | ||
273 | // // Let's assume they all have the same principal | ||
274 | // // Check if it's under the Suitcase folder | ||
275 | // List<InventoryFolderBase> skel = base.GetInventorySkeleton(items[0].Owner); | ||
276 | // InventoryFolderBase suitcase = GetRootFolder(items[0].Owner); | ||
277 | // List<InventoryFolderBase> suitDescendents = GetDescendents(skel, suitcase.ID); | ||
278 | |||
279 | // foreach (InventoryItemBase i in items) | ||
280 | // { | ||
281 | // foreach (InventoryFolderBase f in suitDescendents) | ||
282 | // if (i.Folder == f.ID) | ||
283 | // m_Database.MoveItem(i.ID.ToString(), i.Folder.ToString()); | ||
284 | // } | ||
285 | |||
286 | // return true; | ||
287 | //} | ||
288 | |||
289 | // Let these pass. Use inherited methods. | ||
290 | //public bool DeleteItems(UUID principalID, List<UUID> itemIDs) | ||
291 | //{ | ||
292 | //} | ||
293 | |||
294 | public override InventoryItemBase GetItem(InventoryItemBase item) | ||
295 | { | ||
296 | InventoryItemBase it = base.GetItem(item); | ||
297 | if (it != null) | ||
298 | { | ||
299 | UserAccount user = m_Cache.GetUser(it.CreatorId); | ||
300 | |||
301 | // Adjust the creator data | ||
302 | if (user != null && it != null && string.IsNullOrEmpty(it.CreatorData)) | ||
303 | it.CreatorData = m_HomeURL + ";" + user.FirstName + " " + user.LastName; | ||
304 | } | ||
305 | return it; | ||
306 | } | ||
307 | |||
308 | //public InventoryFolderBase GetFolder(InventoryFolderBase folder) | ||
309 | //{ | ||
310 | //} | ||
311 | |||
312 | //public List<InventoryItemBase> GetActiveGestures(UUID principalID) | ||
313 | //{ | ||
314 | //} | ||
315 | |||
316 | //public int GetAssetPermissions(UUID principalID, UUID assetID) | ||
317 | //{ | ||
318 | //} | ||
319 | |||
320 | } | ||
321 | } | ||
diff --git a/OpenSim/Services/HypergridService/HGSuitcaseInventoryService.cs b/OpenSim/Services/HypergridService/HGSuitcaseInventoryService.cs new file mode 100644 index 0000000..40eb6d4 --- /dev/null +++ b/OpenSim/Services/HypergridService/HGSuitcaseInventoryService.cs | |||
@@ -0,0 +1,652 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Linq; | ||
31 | using OpenMetaverse; | ||
32 | using log4net; | ||
33 | using Nini.Config; | ||
34 | using System.Reflection; | ||
35 | using OpenSim.Services.Base; | ||
36 | using OpenSim.Services.Interfaces; | ||
37 | using OpenSim.Services.InventoryService; | ||
38 | using OpenSim.Data; | ||
39 | using OpenSim.Framework; | ||
40 | using OpenSim.Server.Base; | ||
41 | |||
42 | namespace OpenSim.Services.HypergridService | ||
43 | { | ||
44 | /// <summary> | ||
45 | /// Hypergrid inventory service. It serves the IInventoryService interface, | ||
46 | /// but implements it in ways that are appropriate for inter-grid | ||
47 | /// inventory exchanges. Specifically, it does not performs deletions | ||
48 | /// and it responds to GetRootFolder requests with the ID of the | ||
49 | /// Suitcase folder, not the actual "My Inventory" folder. | ||
50 | /// </summary> | ||
51 | public class HGSuitcaseInventoryService : XInventoryService, IInventoryService | ||
52 | { | ||
53 | private static readonly ILog m_log = | ||
54 | LogManager.GetLogger( | ||
55 | MethodBase.GetCurrentMethod().DeclaringType); | ||
56 | |||
57 | // private string m_HomeURL; | ||
58 | private IUserAccountService m_UserAccountService; | ||
59 | private IAvatarService m_AvatarService; | ||
60 | |||
61 | // private UserAccountCache m_Cache; | ||
62 | |||
63 | private ExpiringCache<UUID, List<XInventoryFolder>> m_SuitcaseTrees = new ExpiringCache<UUID, List<XInventoryFolder>>(); | ||
64 | private ExpiringCache<UUID, AvatarAppearance> m_Appearances = new ExpiringCache<UUID, AvatarAppearance>(); | ||
65 | |||
66 | public HGSuitcaseInventoryService(IConfigSource config, string configName) | ||
67 | : base(config, configName) | ||
68 | { | ||
69 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: Starting with config name {0}", configName); | ||
70 | if (configName != string.Empty) | ||
71 | m_ConfigName = configName; | ||
72 | |||
73 | if (m_Database == null) | ||
74 | m_log.ErrorFormat("[HG SUITCASE INVENTORY SERVICE]: m_Database is null!"); | ||
75 | |||
76 | // | ||
77 | // Try reading the [InventoryService] section, if it exists | ||
78 | // | ||
79 | IConfig invConfig = config.Configs[m_ConfigName]; | ||
80 | if (invConfig != null) | ||
81 | { | ||
82 | string userAccountsDll = invConfig.GetString("UserAccountsService", string.Empty); | ||
83 | if (userAccountsDll == string.Empty) | ||
84 | throw new Exception("Please specify UserAccountsService in HGInventoryService configuration"); | ||
85 | |||
86 | Object[] args = new Object[] { config }; | ||
87 | m_UserAccountService = ServerUtils.LoadPlugin<IUserAccountService>(userAccountsDll, args); | ||
88 | if (m_UserAccountService == null) | ||
89 | throw new Exception(String.Format("Unable to create UserAccountService from {0}", userAccountsDll)); | ||
90 | |||
91 | string avatarDll = invConfig.GetString("AvatarService", string.Empty); | ||
92 | if (avatarDll == string.Empty) | ||
93 | throw new Exception("Please specify AvatarService in HGInventoryService configuration"); | ||
94 | |||
95 | m_AvatarService = ServerUtils.LoadPlugin<IAvatarService>(avatarDll, args); | ||
96 | if (m_AvatarService == null) | ||
97 | throw new Exception(String.Format("Unable to create m_AvatarService from {0}", avatarDll)); | ||
98 | |||
99 | // m_HomeURL = Util.GetConfigVarFromSections<string>(config, "HomeURI", | ||
100 | // new string[] { "Startup", "Hypergrid", m_ConfigName }, String.Empty); | ||
101 | |||
102 | // m_Cache = UserAccountCache.CreateUserAccountCache(m_UserAccountService); | ||
103 | } | ||
104 | |||
105 | m_log.Debug("[HG SUITCASE INVENTORY SERVICE]: Starting..."); | ||
106 | } | ||
107 | |||
108 | public override bool CreateUserInventory(UUID principalID) | ||
109 | { | ||
110 | // NOGO | ||
111 | return false; | ||
112 | } | ||
113 | |||
114 | public override List<InventoryFolderBase> GetInventorySkeleton(UUID principalID) | ||
115 | { | ||
116 | XInventoryFolder suitcase = GetSuitcaseXFolder(principalID); | ||
117 | |||
118 | if (suitcase == null) | ||
119 | { | ||
120 | m_log.WarnFormat("[HG SUITCASE INVENTORY SERVICE]: Found no suitcase folder for user {0} when looking for inventory skeleton", principalID); | ||
121 | return null; | ||
122 | } | ||
123 | |||
124 | List<XInventoryFolder> tree = GetFolderTree(principalID, suitcase.folderID); | ||
125 | if (tree.Count == 0) | ||
126 | return null; | ||
127 | |||
128 | List<InventoryFolderBase> folders = new List<InventoryFolderBase>(); | ||
129 | foreach (XInventoryFolder x in tree) | ||
130 | { | ||
131 | folders.Add(ConvertToOpenSim(x)); | ||
132 | } | ||
133 | |||
134 | SetAsNormalFolder(suitcase); | ||
135 | folders.Add(ConvertToOpenSim(suitcase)); | ||
136 | |||
137 | return folders; | ||
138 | } | ||
139 | |||
140 | public override InventoryFolderBase GetRootFolder(UUID principalID) | ||
141 | { | ||
142 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: GetRootFolder for {0}", principalID); | ||
143 | |||
144 | // Let's find out the local root folder | ||
145 | XInventoryFolder root = GetRootXFolder(principalID); | ||
146 | |||
147 | if (root == null) | ||
148 | { | ||
149 | m_log.WarnFormat("[HG SUITCASE INVENTORY SERVICE]: Unable to retrieve local root folder for user {0}", principalID); | ||
150 | return null; | ||
151 | } | ||
152 | |||
153 | // Warp! Root folder for travelers is the suitcase folder | ||
154 | XInventoryFolder suitcase = GetSuitcaseXFolder(principalID); | ||
155 | |||
156 | if (suitcase == null) | ||
157 | { | ||
158 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: User {0} does not have a Suitcase folder. Creating it...", principalID); | ||
159 | // Create the My Suitcase folder under the user's root folder. | ||
160 | // In the DB we tag it as type 100, but we use type 8 (Folder) outside, as this affects the sort order. | ||
161 | suitcase = CreateFolder(principalID, root.folderID, (int)FolderType.Suitcase, InventoryFolderBase.SUITCASE_FOLDER_NAME); | ||
162 | if (suitcase == null) | ||
163 | { | ||
164 | m_log.ErrorFormat("[HG SUITCASE INVENTORY SERVICE]: Unable to create suitcase folder"); | ||
165 | return null; | ||
166 | } | ||
167 | |||
168 | CreateSystemFolders(principalID, suitcase.folderID); | ||
169 | } | ||
170 | |||
171 | SetAsNormalFolder(suitcase); | ||
172 | |||
173 | return ConvertToOpenSim(suitcase); | ||
174 | } | ||
175 | |||
176 | protected void CreateSystemFolders(UUID principalID, UUID rootID) | ||
177 | { | ||
178 | m_log.Debug("[HG SUITCASE INVENTORY SERVICE]: Creating System folders under Suitcase..."); | ||
179 | XInventoryFolder[] sysFolders = GetSystemFolders(principalID, rootID); | ||
180 | |||
181 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.Animation) return true; return false; })) | ||
182 | CreateFolder(principalID, rootID, (int)FolderType.Animation, "Animations"); | ||
183 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.BodyPart) return true; return false; })) | ||
184 | CreateFolder(principalID, rootID, (int)FolderType.BodyPart, "Body Parts"); | ||
185 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.CallingCard) return true; return false; })) | ||
186 | CreateFolder(principalID, rootID, (int)FolderType.CallingCard, "Calling Cards"); | ||
187 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.Clothing) return true; return false; })) | ||
188 | CreateFolder(principalID, rootID, (int)FolderType.Clothing, "Clothing"); | ||
189 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.CurrentOutfit) return true; return false; })) | ||
190 | CreateFolder(principalID, rootID, (int)FolderType.CurrentOutfit, "Current Outfit"); | ||
191 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.Favorites) return true; return false; })) | ||
192 | CreateFolder(principalID, rootID, (int)FolderType.Favorites, "Favorites"); | ||
193 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.Gesture) return true; return false; })) | ||
194 | CreateFolder(principalID, rootID, (int)FolderType.Gesture, "Gestures"); | ||
195 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.Landmark) return true; return false; })) | ||
196 | CreateFolder(principalID, rootID, (int)FolderType.Landmark, "Landmarks"); | ||
197 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.LostAndFound) return true; return false; })) | ||
198 | CreateFolder(principalID, rootID, (int)FolderType.LostAndFound, "Lost And Found"); | ||
199 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.Notecard) return true; return false; })) | ||
200 | CreateFolder(principalID, rootID, (int)FolderType.Notecard, "Notecards"); | ||
201 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.Object) return true; return false; })) | ||
202 | CreateFolder(principalID, rootID, (int)FolderType.Object, "Objects"); | ||
203 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.Snapshot) return true; return false; })) | ||
204 | CreateFolder(principalID, rootID, (int)FolderType.Snapshot, "Photo Album"); | ||
205 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.LSLText) return true; return false; })) | ||
206 | CreateFolder(principalID, rootID, (int)FolderType.LSLText, "Scripts"); | ||
207 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.Sound) return true; return false; })) | ||
208 | CreateFolder(principalID, rootID, (int)FolderType.Sound, "Sounds"); | ||
209 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.Texture) return true; return false; })) | ||
210 | CreateFolder(principalID, rootID, (int)FolderType.Texture, "Textures"); | ||
211 | if (!Array.Exists(sysFolders, delegate(XInventoryFolder f) { if (f.type == (int)FolderType.Trash) return true; return false; })) | ||
212 | CreateFolder(principalID, rootID, (int)FolderType.Trash, "Trash"); | ||
213 | } | ||
214 | |||
215 | public override InventoryFolderBase GetFolderForType(UUID principalID, FolderType type) | ||
216 | { | ||
217 | //m_log.DebugFormat("[HG INVENTORY SERVICE]: GetFolderForType for {0} {0}", principalID, type); | ||
218 | XInventoryFolder suitcase = GetSuitcaseXFolder(principalID); | ||
219 | |||
220 | if (suitcase == null) | ||
221 | { | ||
222 | m_log.WarnFormat("[HG SUITCASE INVENTORY SERVICE]: Found no suitcase folder for user {0} when looking for child type folder {1}", principalID, type); | ||
223 | return null; | ||
224 | } | ||
225 | |||
226 | XInventoryFolder[] folders = m_Database.GetFolders( | ||
227 | new string[] { "agentID", "type", "parentFolderID" }, | ||
228 | new string[] { principalID.ToString(), ((int)type).ToString(), suitcase.folderID.ToString() }); | ||
229 | |||
230 | if (folders.Length == 0) | ||
231 | { | ||
232 | m_log.WarnFormat("[HG SUITCASE INVENTORY SERVICE]: Found no folder for type {0} for user {1}", type, principalID); | ||
233 | return null; | ||
234 | } | ||
235 | |||
236 | m_log.DebugFormat( | ||
237 | "[HG SUITCASE INVENTORY SERVICE]: Found folder {0} {1} for type {2} for user {3}", | ||
238 | folders[0].folderName, folders[0].folderID, type, principalID); | ||
239 | |||
240 | return ConvertToOpenSim(folders[0]); | ||
241 | } | ||
242 | |||
243 | public override InventoryCollection GetFolderContent(UUID principalID, UUID folderID) | ||
244 | { | ||
245 | InventoryCollection coll = null; | ||
246 | |||
247 | if (!IsWithinSuitcaseTree(principalID, folderID)) | ||
248 | { | ||
249 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: GetFolderContent: folder {0} (user {1}) is not within Suitcase tree", folderID, principalID); | ||
250 | return new InventoryCollection(); | ||
251 | } | ||
252 | |||
253 | coll = base.GetFolderContent(principalID, folderID); | ||
254 | |||
255 | if (coll == null) | ||
256 | { | ||
257 | m_log.WarnFormat("[HG SUITCASE INVENTORY SERVICE]: Something wrong with user {0}'s suitcase folder", principalID); | ||
258 | coll = new InventoryCollection(); | ||
259 | } | ||
260 | return coll; | ||
261 | } | ||
262 | |||
263 | public override List<InventoryItemBase> GetFolderItems(UUID principalID, UUID folderID) | ||
264 | { | ||
265 | // Let's do a bit of sanity checking, more than the base service does | ||
266 | // make sure the given folder exists under the suitcase tree of this user | ||
267 | if (!IsWithinSuitcaseTree(principalID, folderID)) | ||
268 | { | ||
269 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: GetFolderItems: folder {0} (user {1}) is not within Suitcase tree", folderID, principalID); | ||
270 | return new List<InventoryItemBase>(); | ||
271 | } | ||
272 | |||
273 | return base.GetFolderItems(principalID, folderID); | ||
274 | } | ||
275 | |||
276 | public override bool AddFolder(InventoryFolderBase folder) | ||
277 | { | ||
278 | //m_log.WarnFormat("[HG SUITCASE INVENTORY SERVICE]: AddFolder {0} {1}", folder.Name, folder.ParentID); | ||
279 | // Let's do a bit of sanity checking, more than the base service does | ||
280 | // make sure the given folder's parent folder exists under the suitcase tree of this user | ||
281 | |||
282 | if (!IsWithinSuitcaseTree(folder.Owner, folder.ParentID)) | ||
283 | { | ||
284 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: AddFolder: folder {0} (user {1}) is not within Suitcase tree", folder.ParentID, folder.Owner); | ||
285 | return false; | ||
286 | } | ||
287 | |||
288 | // OK, it's legit | ||
289 | if (base.AddFolder(folder)) | ||
290 | { | ||
291 | List<XInventoryFolder> tree; | ||
292 | if (m_SuitcaseTrees.TryGetValue(folder.Owner, out tree)) | ||
293 | tree.Add(ConvertFromOpenSim(folder)); | ||
294 | |||
295 | return true; | ||
296 | } | ||
297 | |||
298 | return false; | ||
299 | } | ||
300 | |||
301 | public override bool UpdateFolder(InventoryFolderBase folder) | ||
302 | { | ||
303 | //m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: Update folder {0}, version {1}", folder.ID, folder.Version); | ||
304 | if (!IsWithinSuitcaseTree(folder.Owner, folder.ID)) | ||
305 | { | ||
306 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: UpdateFolder: folder {0}/{1} (user {2}) is not within Suitcase tree", folder.Name, folder.ID, folder.Owner); | ||
307 | return false; | ||
308 | } | ||
309 | |||
310 | // For all others | ||
311 | return base.UpdateFolder(folder); | ||
312 | } | ||
313 | |||
314 | public override bool MoveFolder(InventoryFolderBase folder) | ||
315 | { | ||
316 | if (!IsWithinSuitcaseTree(folder.Owner, folder.ID)) | ||
317 | { | ||
318 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: MoveFolder: folder {0} (user {1}) is not within Suitcase tree", folder.ID, folder.Owner); | ||
319 | return false; | ||
320 | } | ||
321 | |||
322 | if (!IsWithinSuitcaseTree(folder.Owner, folder.ParentID)) | ||
323 | { | ||
324 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: MoveFolder: folder {0} (user {1}) is not within Suitcase tree", folder.ParentID, folder.Owner); | ||
325 | return false; | ||
326 | } | ||
327 | |||
328 | return base.MoveFolder(folder); | ||
329 | } | ||
330 | |||
331 | public override bool DeleteFolders(UUID principalID, List<UUID> folderIDs) | ||
332 | { | ||
333 | // NOGO | ||
334 | return false; | ||
335 | } | ||
336 | |||
337 | public override bool PurgeFolder(InventoryFolderBase folder) | ||
338 | { | ||
339 | // NOGO | ||
340 | return false; | ||
341 | } | ||
342 | |||
343 | public override bool AddItem(InventoryItemBase item) | ||
344 | { | ||
345 | // Let's do a bit of sanity checking, more than the base service does | ||
346 | // make sure the given folder's parent folder exists under the suitcase tree of this user | ||
347 | if (!IsWithinSuitcaseTree(item.Owner, item.Folder)) | ||
348 | { | ||
349 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: AddItem: folder {0} (user {1}) is not within Suitcase tree", item.Folder, item.Owner); | ||
350 | return false; | ||
351 | } | ||
352 | |||
353 | // OK, it's legit | ||
354 | return base.AddItem(item); | ||
355 | |||
356 | } | ||
357 | |||
358 | public override bool UpdateItem(InventoryItemBase item) | ||
359 | { | ||
360 | if (!IsWithinSuitcaseTree(item.Owner, item.Folder)) | ||
361 | { | ||
362 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: UpdateItem: folder {0} (user {1}) is not within Suitcase tree", item.Folder, item.Owner); | ||
363 | return false; | ||
364 | } | ||
365 | |||
366 | return base.UpdateItem(item); | ||
367 | } | ||
368 | |||
369 | public override bool MoveItems(UUID principalID, List<InventoryItemBase> items) | ||
370 | { | ||
371 | // Principal is b0rked. *sigh* | ||
372 | |||
373 | // Check the items' destination folders | ||
374 | foreach (InventoryItemBase item in items) | ||
375 | { | ||
376 | if (!IsWithinSuitcaseTree(item.Owner, item.Folder)) | ||
377 | { | ||
378 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: MoveItems: folder {0} (user {1}) is not within Suitcase tree", item.Folder, item.Owner); | ||
379 | return false; | ||
380 | } | ||
381 | } | ||
382 | |||
383 | // Check the items' current folders | ||
384 | foreach (InventoryItemBase item in items) | ||
385 | { | ||
386 | InventoryItemBase originalItem = base.GetItem(item); | ||
387 | if (!IsWithinSuitcaseTree(originalItem.Owner, originalItem.Folder)) | ||
388 | { | ||
389 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: MoveItems: folder {0} (user {1}) is not within Suitcase tree", item.Folder, item.Owner); | ||
390 | return false; | ||
391 | } | ||
392 | } | ||
393 | |||
394 | return base.MoveItems(principalID, items); | ||
395 | } | ||
396 | |||
397 | public override bool DeleteItems(UUID principalID, List<UUID> itemIDs) | ||
398 | { | ||
399 | return false; | ||
400 | } | ||
401 | |||
402 | public new InventoryItemBase GetItem(InventoryItemBase item) | ||
403 | { | ||
404 | InventoryItemBase it = base.GetItem(item); | ||
405 | if (it == null) | ||
406 | { | ||
407 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: Unable to retrieve item {0} ({1}) in folder {2}", | ||
408 | item.Name, item.ID, item.Folder); | ||
409 | return null; | ||
410 | } | ||
411 | |||
412 | if (!IsWithinSuitcaseTree(it.Owner, it.Folder) && !IsPartOfAppearance(it.Owner, it.ID)) | ||
413 | { | ||
414 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: GetItem: item {0}/{1} (folder {2}) (user {3}) is not within Suitcase tree or Appearance", | ||
415 | it.Name, it.ID, it.Folder, it.Owner); | ||
416 | return null; | ||
417 | } | ||
418 | |||
419 | // UserAccount user = m_Cache.GetUser(it.CreatorId); | ||
420 | |||
421 | // // Adjust the creator data | ||
422 | // if (user != null && it != null && (it.CreatorData == null || it.CreatorData == string.Empty)) | ||
423 | // it.CreatorData = m_HomeURL + ";" + user.FirstName + " " + user.LastName; | ||
424 | //} | ||
425 | |||
426 | return it; | ||
427 | } | ||
428 | |||
429 | public new InventoryFolderBase GetFolder(InventoryFolderBase folder) | ||
430 | { | ||
431 | InventoryFolderBase f = base.GetFolder(folder); | ||
432 | |||
433 | if (f != null) | ||
434 | { | ||
435 | if (!IsWithinSuitcaseTree(f.Owner, f.ID)) | ||
436 | { | ||
437 | m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: GetFolder: folder {0}/{1} (user {2}) is not within Suitcase tree", | ||
438 | f.Name, f.ID, f.Owner); | ||
439 | return null; | ||
440 | } | ||
441 | } | ||
442 | |||
443 | return f; | ||
444 | } | ||
445 | |||
446 | //public List<InventoryItemBase> GetActiveGestures(UUID principalID) | ||
447 | //{ | ||
448 | //} | ||
449 | |||
450 | //public int GetAssetPermissions(UUID principalID, UUID assetID) | ||
451 | //{ | ||
452 | //} | ||
453 | |||
454 | #region Auxiliary functions | ||
455 | private XInventoryFolder GetXFolder(UUID userID, UUID folderID) | ||
456 | { | ||
457 | XInventoryFolder[] folders = m_Database.GetFolders( | ||
458 | new string[] { "agentID", "folderID" }, | ||
459 | new string[] { userID.ToString(), folderID.ToString() }); | ||
460 | |||
461 | if (folders.Length == 0) | ||
462 | return null; | ||
463 | |||
464 | return folders[0]; | ||
465 | } | ||
466 | |||
467 | private XInventoryFolder GetRootXFolder(UUID principalID) | ||
468 | { | ||
469 | XInventoryFolder[] folders = m_Database.GetFolders( | ||
470 | new string[] { "agentID", "folderName", "type" }, | ||
471 | new string[] { principalID.ToString(), InventoryFolderBase.ROOT_FOLDER_NAME, ((int)FolderType.Root).ToString() }); | ||
472 | |||
473 | if (folders != null && folders.Length > 0) | ||
474 | return folders[0]; | ||
475 | |||
476 | // OK, so the RootFolder type didn't work. Let's look for any type with parent UUID.Zero. | ||
477 | folders = m_Database.GetFolders( | ||
478 | new string[] { "agentID", "folderName", "parentFolderID" }, | ||
479 | new string[] { principalID.ToString(), InventoryFolderBase.ROOT_FOLDER_NAME, UUID.Zero.ToString() }); | ||
480 | |||
481 | if (folders != null && folders.Length > 0) | ||
482 | return folders[0]; | ||
483 | |||
484 | return null; | ||
485 | } | ||
486 | |||
487 | private XInventoryFolder GetCurrentOutfitXFolder(UUID userID) | ||
488 | { | ||
489 | XInventoryFolder root = GetRootXFolder(userID); | ||
490 | if (root == null) | ||
491 | return null; | ||
492 | |||
493 | XInventoryFolder[] folders = m_Database.GetFolders( | ||
494 | new string[] { "agentID", "type", "parentFolderID" }, | ||
495 | new string[] { userID.ToString(), ((int)FolderType.CurrentOutfit).ToString(), root.folderID.ToString() }); | ||
496 | |||
497 | if (folders.Length == 0) | ||
498 | return null; | ||
499 | |||
500 | return folders[0]; | ||
501 | } | ||
502 | |||
503 | private XInventoryFolder GetSuitcaseXFolder(UUID principalID) | ||
504 | { | ||
505 | // Warp! Root folder for travelers | ||
506 | XInventoryFolder[] folders = m_Database.GetFolders( | ||
507 | new string[] { "agentID", "type" }, | ||
508 | new string[] { principalID.ToString(), ((int)FolderType.Suitcase).ToString() }); | ||
509 | |||
510 | if (folders != null && folders.Length > 0) | ||
511 | return folders[0]; | ||
512 | |||
513 | // check to see if we have the old Suitcase folder | ||
514 | folders = m_Database.GetFolders( | ||
515 | new string[] { "agentID", "folderName", "parentFolderID" }, | ||
516 | new string[] { principalID.ToString(), InventoryFolderBase.SUITCASE_FOLDER_NAME, UUID.Zero.ToString() }); | ||
517 | if (folders != null && folders.Length > 0) | ||
518 | { | ||
519 | // Move it to under the root folder | ||
520 | XInventoryFolder root = GetRootXFolder(principalID); | ||
521 | folders[0].parentFolderID = root.folderID; | ||
522 | folders[0].type = (int)FolderType.Suitcase; | ||
523 | m_Database.StoreFolder(folders[0]); | ||
524 | return folders[0]; | ||
525 | } | ||
526 | |||
527 | return null; | ||
528 | } | ||
529 | |||
530 | private void SetAsNormalFolder(XInventoryFolder suitcase) | ||
531 | { | ||
532 | //suitcase.type = InventoryItemBase.SUITCASE_FOLDER_FAKE_TYPE; | ||
533 | } | ||
534 | |||
535 | private List<XInventoryFolder> GetFolderTree(UUID principalID, UUID folder) | ||
536 | { | ||
537 | List<XInventoryFolder> t; | ||
538 | if (m_SuitcaseTrees.TryGetValue(principalID, out t)) | ||
539 | return t; | ||
540 | |||
541 | // Get the tree of the suitcase folder | ||
542 | t = GetFolderTreeRecursive(folder); | ||
543 | m_SuitcaseTrees.AddOrUpdate(principalID, t, 5*60); // 5 minutes | ||
544 | return t; | ||
545 | } | ||
546 | |||
547 | private List<XInventoryFolder> GetFolderTreeRecursive(UUID root) | ||
548 | { | ||
549 | List<XInventoryFolder> tree = new List<XInventoryFolder>(); | ||
550 | XInventoryFolder[] folders = m_Database.GetFolders( | ||
551 | new string[] { "parentFolderID" }, | ||
552 | new string[] { root.ToString() }); | ||
553 | |||
554 | if (folders == null || folders.Length == 0) | ||
555 | { | ||
556 | return tree; // empty tree | ||
557 | } | ||
558 | else | ||
559 | { | ||
560 | foreach (XInventoryFolder f in folders) | ||
561 | { | ||
562 | tree.Add(f); | ||
563 | tree.AddRange(GetFolderTreeRecursive(f.folderID)); | ||
564 | } | ||
565 | return tree; | ||
566 | } | ||
567 | |||
568 | } | ||
569 | |||
570 | /// <summary> | ||
571 | /// Return true if the folderID is a subfolder of the Suitcase or the suitcase folder itself | ||
572 | /// </summary> | ||
573 | /// <param name="folderID"></param> | ||
574 | /// <param name="root"></param> | ||
575 | /// <param name="suitcase"></param> | ||
576 | /// <returns></returns> | ||
577 | private bool IsWithinSuitcaseTree(UUID principalID, UUID folderID) | ||
578 | { | ||
579 | XInventoryFolder suitcase = GetSuitcaseXFolder(principalID); | ||
580 | |||
581 | if (suitcase == null) | ||
582 | { | ||
583 | m_log.WarnFormat("[HG SUITCASE INVENTORY SERVICE]: User {0} does not have a Suitcase folder", principalID); | ||
584 | return false; | ||
585 | } | ||
586 | |||
587 | List<XInventoryFolder> tree = new List<XInventoryFolder>(); | ||
588 | tree.Add(suitcase); // Warp! the tree is the real root folder plus the children of the suitcase folder | ||
589 | tree.AddRange(GetFolderTree(principalID, suitcase.folderID)); | ||
590 | |||
591 | // Also add the Current Outfit folder to the list of available folders | ||
592 | XInventoryFolder folder = GetCurrentOutfitXFolder(principalID); | ||
593 | if (folder != null) | ||
594 | tree.Add(folder); | ||
595 | |||
596 | XInventoryFolder f = tree.Find(delegate(XInventoryFolder fl) | ||
597 | { | ||
598 | return (fl.folderID == folderID); | ||
599 | }); | ||
600 | |||
601 | return (f != null); | ||
602 | } | ||
603 | #endregion | ||
604 | |||
605 | #region Avatar Appearance | ||
606 | |||
607 | private AvatarAppearance GetAppearance(UUID principalID) | ||
608 | { | ||
609 | AvatarAppearance a = null; | ||
610 | if (m_Appearances.TryGetValue(principalID, out a)) | ||
611 | return a; | ||
612 | |||
613 | a = m_AvatarService.GetAppearance(principalID); | ||
614 | m_Appearances.AddOrUpdate(principalID, a, 5 * 60); // 5minutes | ||
615 | return a; | ||
616 | } | ||
617 | |||
618 | private bool IsPartOfAppearance(UUID principalID, UUID itemID) | ||
619 | { | ||
620 | AvatarAppearance a = GetAppearance(principalID); | ||
621 | |||
622 | if (a == null) | ||
623 | return false; | ||
624 | |||
625 | // Check wearables (body parts and clothes) | ||
626 | for (int i = 0; i < a.Wearables.Length; i++) | ||
627 | { | ||
628 | for (int j = 0; j < a.Wearables[i].Count; j++) | ||
629 | { | ||
630 | if (a.Wearables[i][j].ItemID == itemID) | ||
631 | { | ||
632 | //m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: item {0} is a wearable", itemID); | ||
633 | return true; | ||
634 | } | ||
635 | } | ||
636 | } | ||
637 | |||
638 | // Check attachments | ||
639 | if (a.GetAttachmentForItem(itemID) != null) | ||
640 | { | ||
641 | //m_log.DebugFormat("[HG SUITCASE INVENTORY SERVICE]: item {0} is an attachment", itemID); | ||
642 | return true; | ||
643 | } | ||
644 | |||
645 | return false; | ||
646 | } | ||
647 | |||
648 | #endregion | ||
649 | |||
650 | } | ||
651 | |||
652 | } | ||
diff --git a/OpenSim/Services/HypergridService/Properties/AssemblyInfo.cs b/OpenSim/Services/HypergridService/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a565729 --- /dev/null +++ b/OpenSim/Services/HypergridService/Properties/AssemblyInfo.cs | |||
@@ -0,0 +1,33 @@ | |||
1 | using System.Reflection; | ||
2 | using System.Runtime.CompilerServices; | ||
3 | using System.Runtime.InteropServices; | ||
4 | |||
5 | // General Information about an assembly is controlled through the following | ||
6 | // set of attributes. Change these attribute values to modify the information | ||
7 | // associated with an assembly. | ||
8 | [assembly: AssemblyTitle("OpenSim.Services.HypergridService")] | ||
9 | [assembly: AssemblyDescription("")] | ||
10 | [assembly: AssemblyConfiguration("")] | ||
11 | [assembly: AssemblyCompany("http://opensimulator.org")] | ||
12 | [assembly: AssemblyProduct("OpenSim")] | ||
13 | [assembly: AssemblyCopyright("OpenSimulator developers")] | ||
14 | [assembly: AssemblyTrademark("")] | ||
15 | [assembly: AssemblyCulture("")] | ||
16 | |||
17 | // Setting ComVisible to false makes the types in this assembly not visible | ||
18 | // to COM components. If you need to access a type in this assembly from | ||
19 | // COM, set the ComVisible attribute to true on that type. | ||
20 | [assembly: ComVisible(false)] | ||
21 | |||
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM | ||
23 | [assembly: Guid("8584f3c1-26dd-4d95-86f4-cd8f0110a18f")] | ||
24 | |||
25 | // Version information for an assembly consists of the following four values: | ||
26 | // | ||
27 | // Major Version | ||
28 | // Minor Version | ||
29 | // Build Number | ||
30 | // Revision | ||
31 | // | ||
32 | [assembly: AssemblyVersion("0.8.2.*")] | ||
33 | |||
diff --git a/OpenSim/Services/HypergridService/UserAccountCache.cs b/OpenSim/Services/HypergridService/UserAccountCache.cs new file mode 100644 index 0000000..fa7dd0b --- /dev/null +++ b/OpenSim/Services/HypergridService/UserAccountCache.cs | |||
@@ -0,0 +1,111 @@ | |||
1 | using System; | ||
2 | using System.Collections.Generic; | ||
3 | using System.Reflection; | ||
4 | |||
5 | using log4net; | ||
6 | using OpenMetaverse; | ||
7 | |||
8 | using OpenSim.Services.Interfaces; | ||
9 | |||
10 | namespace OpenSim.Services.HypergridService | ||
11 | { | ||
12 | public class UserAccountCache : IUserAccountService | ||
13 | { | ||
14 | private const double CACHE_EXPIRATION_SECONDS = 120000.0; // 33 hours! | ||
15 | |||
16 | // private static readonly ILog m_log = | ||
17 | // LogManager.GetLogger( | ||
18 | // MethodBase.GetCurrentMethod().DeclaringType); | ||
19 | |||
20 | private ExpiringCache<UUID, UserAccount> m_UUIDCache; | ||
21 | |||
22 | private IUserAccountService m_UserAccountService; | ||
23 | |||
24 | private static UserAccountCache m_Singleton; | ||
25 | |||
26 | public static UserAccountCache CreateUserAccountCache(IUserAccountService u) | ||
27 | { | ||
28 | if (m_Singleton == null) | ||
29 | m_Singleton = new UserAccountCache(u); | ||
30 | |||
31 | return m_Singleton; | ||
32 | } | ||
33 | |||
34 | private UserAccountCache(IUserAccountService u) | ||
35 | { | ||
36 | m_UUIDCache = new ExpiringCache<UUID, UserAccount>(); | ||
37 | m_UserAccountService = u; | ||
38 | } | ||
39 | |||
40 | public void Cache(UUID userID, UserAccount account) | ||
41 | { | ||
42 | // Cache even null accounts | ||
43 | m_UUIDCache.AddOrUpdate(userID, account, CACHE_EXPIRATION_SECONDS); | ||
44 | |||
45 | //m_log.DebugFormat("[USER CACHE]: cached user {0}", userID); | ||
46 | } | ||
47 | |||
48 | public UserAccount Get(UUID userID, out bool inCache) | ||
49 | { | ||
50 | UserAccount account = null; | ||
51 | inCache = false; | ||
52 | if (m_UUIDCache.TryGetValue(userID, out account)) | ||
53 | { | ||
54 | //m_log.DebugFormat("[USER CACHE]: Account {0} {1} found in cache", account.FirstName, account.LastName); | ||
55 | inCache = true; | ||
56 | return account; | ||
57 | } | ||
58 | |||
59 | return null; | ||
60 | } | ||
61 | |||
62 | public UserAccount GetUser(string id) | ||
63 | { | ||
64 | UUID uuid = UUID.Zero; | ||
65 | UUID.TryParse(id, out uuid); | ||
66 | bool inCache = false; | ||
67 | UserAccount account = Get(uuid, out inCache); | ||
68 | if (!inCache) | ||
69 | { | ||
70 | account = m_UserAccountService.GetUserAccount(UUID.Zero, uuid); | ||
71 | Cache(uuid, account); | ||
72 | } | ||
73 | |||
74 | return account; | ||
75 | } | ||
76 | |||
77 | #region IUserAccountService | ||
78 | public UserAccount GetUserAccount(UUID scopeID, UUID userID) | ||
79 | { | ||
80 | return GetUser(userID.ToString()); | ||
81 | } | ||
82 | |||
83 | public UserAccount GetUserAccount(UUID scopeID, string FirstName, string LastName) | ||
84 | { | ||
85 | return null; | ||
86 | } | ||
87 | |||
88 | public UserAccount GetUserAccount(UUID scopeID, string Email) | ||
89 | { | ||
90 | return null; | ||
91 | } | ||
92 | |||
93 | public List<UserAccount> GetUserAccounts(UUID scopeID, string query) | ||
94 | { | ||
95 | return null; | ||
96 | } | ||
97 | |||
98 | public void InvalidateCache(UUID userID) | ||
99 | { | ||
100 | m_UUIDCache.Remove(userID); | ||
101 | } | ||
102 | |||
103 | public bool StoreUserAccount(UserAccount data) | ||
104 | { | ||
105 | return false; | ||
106 | } | ||
107 | #endregion | ||
108 | |||
109 | } | ||
110 | |||
111 | } | ||
diff --git a/OpenSim/Services/HypergridService/UserAgentService.cs b/OpenSim/Services/HypergridService/UserAgentService.cs new file mode 100644 index 0000000..c65122a --- /dev/null +++ b/OpenSim/Services/HypergridService/UserAgentService.cs | |||
@@ -0,0 +1,746 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Net; | ||
31 | using System.Reflection; | ||
32 | |||
33 | using OpenSim.Data; | ||
34 | using OpenSim.Framework; | ||
35 | using OpenSim.Services.Connectors.Friends; | ||
36 | using OpenSim.Services.Connectors.Hypergrid; | ||
37 | using OpenSim.Services.Interfaces; | ||
38 | using GridRegion = OpenSim.Services.Interfaces.GridRegion; | ||
39 | using OpenSim.Server.Base; | ||
40 | using FriendInfo = OpenSim.Services.Interfaces.FriendInfo; | ||
41 | |||
42 | using OpenMetaverse; | ||
43 | using log4net; | ||
44 | using Nini.Config; | ||
45 | |||
46 | namespace OpenSim.Services.HypergridService | ||
47 | { | ||
48 | /// <summary> | ||
49 | /// This service is for HG1.5 only, to make up for the fact that clients don't | ||
50 | /// keep any private information in themselves, and that their 'home service' | ||
51 | /// needs to do it for them. | ||
52 | /// Once we have better clients, this shouldn't be needed. | ||
53 | /// </summary> | ||
54 | public class UserAgentService : UserAgentServiceBase, IUserAgentService | ||
55 | { | ||
56 | private static readonly ILog m_log = | ||
57 | LogManager.GetLogger( | ||
58 | MethodBase.GetCurrentMethod().DeclaringType); | ||
59 | |||
60 | // This will need to go into a DB table | ||
61 | //static Dictionary<UUID, TravelingAgentInfo> m_Database = new Dictionary<UUID, TravelingAgentInfo>(); | ||
62 | |||
63 | static bool m_Initialized = false; | ||
64 | |||
65 | protected static IGridUserService m_GridUserService; | ||
66 | protected static IGridService m_GridService; | ||
67 | protected static GatekeeperServiceConnector m_GatekeeperConnector; | ||
68 | protected static IGatekeeperService m_GatekeeperService; | ||
69 | protected static IFriendsService m_FriendsService; | ||
70 | protected static IPresenceService m_PresenceService; | ||
71 | protected static IUserAccountService m_UserAccountService; | ||
72 | protected static IFriendsSimConnector m_FriendsLocalSimConnector; // standalone, points to HGFriendsModule | ||
73 | protected static FriendsSimConnector m_FriendsSimConnector; // grid | ||
74 | |||
75 | protected static string m_GridName; | ||
76 | |||
77 | protected static int m_LevelOutsideContacts; | ||
78 | protected static bool m_ShowDetails; | ||
79 | |||
80 | protected static bool m_BypassClientVerification; | ||
81 | |||
82 | private static Dictionary<int, bool> m_ForeignTripsAllowed = new Dictionary<int, bool>(); | ||
83 | private static Dictionary<int, List<string>> m_TripsAllowedExceptions = new Dictionary<int, List<string>>(); | ||
84 | private static Dictionary<int, List<string>> m_TripsDisallowedExceptions = new Dictionary<int, List<string>>(); | ||
85 | |||
86 | public UserAgentService(IConfigSource config) : this(config, null) | ||
87 | { | ||
88 | } | ||
89 | |||
90 | public UserAgentService(IConfigSource config, IFriendsSimConnector friendsConnector) | ||
91 | : base(config) | ||
92 | { | ||
93 | // Let's set this always, because we don't know the sequence | ||
94 | // of instantiations | ||
95 | if (friendsConnector != null) | ||
96 | m_FriendsLocalSimConnector = friendsConnector; | ||
97 | |||
98 | if (!m_Initialized) | ||
99 | { | ||
100 | m_Initialized = true; | ||
101 | |||
102 | m_log.DebugFormat("[HOME USERS SECURITY]: Starting..."); | ||
103 | |||
104 | m_FriendsSimConnector = new FriendsSimConnector(); | ||
105 | |||
106 | IConfig serverConfig = config.Configs["UserAgentService"]; | ||
107 | if (serverConfig == null) | ||
108 | throw new Exception(String.Format("No section UserAgentService in config file")); | ||
109 | |||
110 | string gridService = serverConfig.GetString("GridService", String.Empty); | ||
111 | string gridUserService = serverConfig.GetString("GridUserService", String.Empty); | ||
112 | string gatekeeperService = serverConfig.GetString("GatekeeperService", String.Empty); | ||
113 | string friendsService = serverConfig.GetString("FriendsService", String.Empty); | ||
114 | string presenceService = serverConfig.GetString("PresenceService", String.Empty); | ||
115 | string userAccountService = serverConfig.GetString("UserAccountService", String.Empty); | ||
116 | |||
117 | m_BypassClientVerification = serverConfig.GetBoolean("BypassClientVerification", false); | ||
118 | |||
119 | if (gridService == string.Empty || gridUserService == string.Empty || gatekeeperService == string.Empty) | ||
120 | throw new Exception(String.Format("Incomplete specifications, UserAgent Service cannot function.")); | ||
121 | |||
122 | Object[] args = new Object[] { config }; | ||
123 | m_GridService = ServerUtils.LoadPlugin<IGridService>(gridService, args); | ||
124 | m_GridUserService = ServerUtils.LoadPlugin<IGridUserService>(gridUserService, args); | ||
125 | m_GatekeeperConnector = new GatekeeperServiceConnector(); | ||
126 | m_GatekeeperService = ServerUtils.LoadPlugin<IGatekeeperService>(gatekeeperService, args); | ||
127 | m_FriendsService = ServerUtils.LoadPlugin<IFriendsService>(friendsService, args); | ||
128 | m_PresenceService = ServerUtils.LoadPlugin<IPresenceService>(presenceService, args); | ||
129 | m_UserAccountService = ServerUtils.LoadPlugin<IUserAccountService>(userAccountService, args); | ||
130 | |||
131 | m_LevelOutsideContacts = serverConfig.GetInt("LevelOutsideContacts", 0); | ||
132 | m_ShowDetails = serverConfig.GetBoolean("ShowUserDetailsInHGProfile", true); | ||
133 | |||
134 | LoadTripPermissionsFromConfig(serverConfig, "ForeignTripsAllowed"); | ||
135 | LoadDomainExceptionsFromConfig(serverConfig, "AllowExcept", m_TripsAllowedExceptions); | ||
136 | LoadDomainExceptionsFromConfig(serverConfig, "DisallowExcept", m_TripsDisallowedExceptions); | ||
137 | |||
138 | m_GridName = Util.GetConfigVarFromSections<string>(config, "GatekeeperURI", | ||
139 | new string[] { "Startup", "Hypergrid", "UserAgentService" }, String.Empty); | ||
140 | if (string.IsNullOrEmpty(m_GridName)) // Legacy. Remove soon. | ||
141 | { | ||
142 | m_GridName = serverConfig.GetString("ExternalName", string.Empty); | ||
143 | if (m_GridName == string.Empty) | ||
144 | { | ||
145 | serverConfig = config.Configs["GatekeeperService"]; | ||
146 | m_GridName = serverConfig.GetString("ExternalName", string.Empty); | ||
147 | } | ||
148 | } | ||
149 | |||
150 | if (!m_GridName.EndsWith("/")) | ||
151 | m_GridName = m_GridName + "/"; | ||
152 | |||
153 | // Finally some cleanup | ||
154 | m_Database.DeleteOld(); | ||
155 | |||
156 | } | ||
157 | } | ||
158 | |||
159 | protected void LoadTripPermissionsFromConfig(IConfig config, string variable) | ||
160 | { | ||
161 | foreach (string keyName in config.GetKeys()) | ||
162 | { | ||
163 | if (keyName.StartsWith(variable + "_Level_")) | ||
164 | { | ||
165 | int level = 0; | ||
166 | if (Int32.TryParse(keyName.Replace(variable + "_Level_", ""), out level)) | ||
167 | m_ForeignTripsAllowed.Add(level, config.GetBoolean(keyName, true)); | ||
168 | } | ||
169 | } | ||
170 | } | ||
171 | |||
172 | protected void LoadDomainExceptionsFromConfig(IConfig config, string variable, Dictionary<int, List<string>> exceptions) | ||
173 | { | ||
174 | foreach (string keyName in config.GetKeys()) | ||
175 | { | ||
176 | if (keyName.StartsWith(variable + "_Level_")) | ||
177 | { | ||
178 | int level = 0; | ||
179 | if (Int32.TryParse(keyName.Replace(variable + "_Level_", ""), out level) && !exceptions.ContainsKey(level)) | ||
180 | { | ||
181 | exceptions.Add(level, new List<string>()); | ||
182 | string value = config.GetString(keyName, string.Empty); | ||
183 | string[] parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); | ||
184 | |||
185 | foreach (string s in parts) | ||
186 | exceptions[level].Add(s.Trim()); | ||
187 | } | ||
188 | } | ||
189 | } | ||
190 | } | ||
191 | |||
192 | |||
193 | public GridRegion GetHomeRegion(UUID userID, out Vector3 position, out Vector3 lookAt) | ||
194 | { | ||
195 | position = new Vector3(128, 128, 0); lookAt = Vector3.UnitY; | ||
196 | |||
197 | m_log.DebugFormat("[USER AGENT SERVICE]: Request to get home region of user {0}", userID); | ||
198 | |||
199 | GridRegion home = null; | ||
200 | GridUserInfo uinfo = m_GridUserService.GetGridUserInfo(userID.ToString()); | ||
201 | if (uinfo != null) | ||
202 | { | ||
203 | if (uinfo.HomeRegionID != UUID.Zero) | ||
204 | { | ||
205 | home = m_GridService.GetRegionByUUID(UUID.Zero, uinfo.HomeRegionID); | ||
206 | position = uinfo.HomePosition; | ||
207 | lookAt = uinfo.HomeLookAt; | ||
208 | } | ||
209 | if (home == null) | ||
210 | { | ||
211 | List<GridRegion> defs = m_GridService.GetDefaultRegions(UUID.Zero); | ||
212 | if (defs != null && defs.Count > 0) | ||
213 | home = defs[0]; | ||
214 | } | ||
215 | } | ||
216 | |||
217 | return home; | ||
218 | } | ||
219 | |||
220 | public bool LoginAgentToGrid(GridRegion source, AgentCircuitData agentCircuit, GridRegion gatekeeper, GridRegion finalDestination, bool fromLogin, out string reason) | ||
221 | { | ||
222 | m_log.DebugFormat("[USER AGENT SERVICE]: Request to login user {0} {1} (@{2}) to grid {3}", | ||
223 | agentCircuit.firstname, agentCircuit.lastname, (fromLogin ? agentCircuit.IPAddress : "stored IP"), gatekeeper.ServerURI); | ||
224 | |||
225 | string gridName = gatekeeper.ServerURI; | ||
226 | |||
227 | UserAccount account = m_UserAccountService.GetUserAccount(UUID.Zero, agentCircuit.AgentID); | ||
228 | if (account == null) | ||
229 | { | ||
230 | m_log.WarnFormat("[USER AGENT SERVICE]: Someone attempted to lauch a foreign user from here {0} {1}", agentCircuit.firstname, agentCircuit.lastname); | ||
231 | reason = "Forbidden to launch your agents from here"; | ||
232 | return false; | ||
233 | } | ||
234 | |||
235 | // Is this user allowed to go there? | ||
236 | if (m_GridName != gridName) | ||
237 | { | ||
238 | if (m_ForeignTripsAllowed.ContainsKey(account.UserLevel)) | ||
239 | { | ||
240 | bool allowed = m_ForeignTripsAllowed[account.UserLevel]; | ||
241 | |||
242 | if (m_ForeignTripsAllowed[account.UserLevel] && IsException(gridName, account.UserLevel, m_TripsAllowedExceptions)) | ||
243 | allowed = false; | ||
244 | |||
245 | if (!m_ForeignTripsAllowed[account.UserLevel] && IsException(gridName, account.UserLevel, m_TripsDisallowedExceptions)) | ||
246 | allowed = true; | ||
247 | |||
248 | if (!allowed) | ||
249 | { | ||
250 | reason = "Your world does not allow you to visit the destination"; | ||
251 | m_log.InfoFormat("[USER AGENT SERVICE]: Agents not permitted to visit {0}. Refusing service.", gridName); | ||
252 | return false; | ||
253 | } | ||
254 | } | ||
255 | } | ||
256 | |||
257 | |||
258 | // Take the IP address + port of the gatekeeper (reg) plus the info of finalDestination | ||
259 | GridRegion region = new GridRegion(gatekeeper); | ||
260 | region.ServerURI = gatekeeper.ServerURI; | ||
261 | region.ExternalHostName = finalDestination.ExternalHostName; | ||
262 | region.InternalEndPoint = finalDestination.InternalEndPoint; | ||
263 | region.RegionName = finalDestination.RegionName; | ||
264 | region.RegionID = finalDestination.RegionID; | ||
265 | region.RegionLocX = finalDestination.RegionLocX; | ||
266 | region.RegionLocY = finalDestination.RegionLocY; | ||
267 | |||
268 | // Generate a new service session | ||
269 | agentCircuit.ServiceSessionID = region.ServerURI + ";" + UUID.Random(); | ||
270 | TravelingAgentInfo old = null; | ||
271 | TravelingAgentInfo travel = CreateTravelInfo(agentCircuit, region, fromLogin, out old); | ||
272 | |||
273 | bool success = false; | ||
274 | string myExternalIP = string.Empty; | ||
275 | |||
276 | m_log.DebugFormat("[USER AGENT SERVICE]: this grid: {0}, desired grid: {1}, desired region: {2}", m_GridName, gridName, region.RegionID); | ||
277 | |||
278 | if (m_GridName == gridName) | ||
279 | { | ||
280 | success = m_GatekeeperService.LoginAgent(source, agentCircuit, finalDestination, out reason); | ||
281 | } | ||
282 | else | ||
283 | { | ||
284 | success = m_GatekeeperConnector.CreateAgent(source, region, agentCircuit, (uint)Constants.TeleportFlags.ViaLogin, out myExternalIP, out reason); | ||
285 | } | ||
286 | |||
287 | if (!success) | ||
288 | { | ||
289 | m_log.DebugFormat("[USER AGENT SERVICE]: Unable to login user {0} {1} to grid {2}, reason: {3}", | ||
290 | agentCircuit.firstname, agentCircuit.lastname, region.ServerURI, reason); | ||
291 | |||
292 | if (old != null) | ||
293 | StoreTravelInfo(old); | ||
294 | else | ||
295 | m_Database.Delete(agentCircuit.SessionID); | ||
296 | |||
297 | return false; | ||
298 | } | ||
299 | |||
300 | // Everything is ok | ||
301 | |||
302 | // Update the perceived IP Address of our grid | ||
303 | m_log.DebugFormat("[USER AGENT SERVICE]: Gatekeeper sees me as {0}", myExternalIP); | ||
304 | travel.MyIpAddress = myExternalIP; | ||
305 | |||
306 | StoreTravelInfo(travel); | ||
307 | |||
308 | return true; | ||
309 | } | ||
310 | |||
311 | public bool LoginAgentToGrid(GridRegion source, AgentCircuitData agentCircuit, GridRegion gatekeeper, GridRegion finalDestination, out string reason) | ||
312 | { | ||
313 | reason = string.Empty; | ||
314 | return LoginAgentToGrid(source, agentCircuit, gatekeeper, finalDestination, false, out reason); | ||
315 | } | ||
316 | |||
317 | TravelingAgentInfo CreateTravelInfo(AgentCircuitData agentCircuit, GridRegion region, bool fromLogin, out TravelingAgentInfo existing) | ||
318 | { | ||
319 | HGTravelingData hgt = m_Database.Get(agentCircuit.SessionID); | ||
320 | existing = null; | ||
321 | |||
322 | if (hgt != null) | ||
323 | { | ||
324 | // Very important! Override whatever this agent comes with. | ||
325 | // UserAgentService always sets the IP for every new agent | ||
326 | // with the original IP address. | ||
327 | existing = new TravelingAgentInfo(hgt); | ||
328 | agentCircuit.IPAddress = existing.ClientIPAddress; | ||
329 | } | ||
330 | |||
331 | TravelingAgentInfo travel = new TravelingAgentInfo(existing); | ||
332 | travel.SessionID = agentCircuit.SessionID; | ||
333 | travel.UserID = agentCircuit.AgentID; | ||
334 | travel.GridExternalName = region.ServerURI; | ||
335 | travel.ServiceToken = agentCircuit.ServiceSessionID; | ||
336 | |||
337 | if (fromLogin) | ||
338 | travel.ClientIPAddress = agentCircuit.IPAddress; | ||
339 | |||
340 | StoreTravelInfo(travel); | ||
341 | |||
342 | return travel; | ||
343 | } | ||
344 | |||
345 | public void LogoutAgent(UUID userID, UUID sessionID) | ||
346 | { | ||
347 | m_log.DebugFormat("[USER AGENT SERVICE]: User {0} logged out", userID); | ||
348 | |||
349 | m_Database.Delete(sessionID); | ||
350 | |||
351 | GridUserInfo guinfo = m_GridUserService.GetGridUserInfo(userID.ToString()); | ||
352 | if (guinfo != null) | ||
353 | m_GridUserService.LoggedOut(userID.ToString(), sessionID, guinfo.LastRegionID, guinfo.LastPosition, guinfo.LastLookAt); | ||
354 | } | ||
355 | |||
356 | // We need to prevent foreign users with the same UUID as a local user | ||
357 | public bool IsAgentComingHome(UUID sessionID, string thisGridExternalName) | ||
358 | { | ||
359 | HGTravelingData hgt = m_Database.Get(sessionID); | ||
360 | if (hgt == null) | ||
361 | return false; | ||
362 | |||
363 | TravelingAgentInfo travel = new TravelingAgentInfo(hgt); | ||
364 | |||
365 | return travel.GridExternalName.ToLower() == thisGridExternalName.ToLower(); | ||
366 | } | ||
367 | |||
368 | public bool VerifyClient(UUID sessionID, string reportedIP) | ||
369 | { | ||
370 | if (m_BypassClientVerification) | ||
371 | return true; | ||
372 | |||
373 | m_log.DebugFormat("[USER AGENT SERVICE]: Verifying Client session {0} with reported IP {1}.", | ||
374 | sessionID, reportedIP); | ||
375 | |||
376 | HGTravelingData hgt = m_Database.Get(sessionID); | ||
377 | if (hgt == null) | ||
378 | return false; | ||
379 | |||
380 | TravelingAgentInfo travel = new TravelingAgentInfo(hgt); | ||
381 | |||
382 | bool result = travel.ClientIPAddress == reportedIP || travel.MyIpAddress == reportedIP; // NATed | ||
383 | |||
384 | m_log.DebugFormat("[USER AGENT SERVICE]: Comparing {0} with login IP {1} and MyIP {1}; result is {3}", | ||
385 | reportedIP, travel.ClientIPAddress, travel.MyIpAddress, result); | ||
386 | |||
387 | return result; | ||
388 | } | ||
389 | |||
390 | public bool VerifyAgent(UUID sessionID, string token) | ||
391 | { | ||
392 | HGTravelingData hgt = m_Database.Get(sessionID); | ||
393 | if (hgt == null) | ||
394 | { | ||
395 | m_log.DebugFormat("[USER AGENT SERVICE]: Token verification for session {0}: no such session", sessionID); | ||
396 | return false; | ||
397 | } | ||
398 | |||
399 | TravelingAgentInfo travel = new TravelingAgentInfo(hgt); | ||
400 | m_log.DebugFormat("[USER AGENT SERVICE]: Verifying agent token {0} against {1}", token, travel.ServiceToken); | ||
401 | return travel.ServiceToken == token; | ||
402 | } | ||
403 | |||
404 | [Obsolete] | ||
405 | public List<UUID> StatusNotification(List<string> friends, UUID foreignUserID, bool online) | ||
406 | { | ||
407 | if (m_FriendsService == null || m_PresenceService == null) | ||
408 | { | ||
409 | m_log.WarnFormat("[USER AGENT SERVICE]: Unable to perform status notifications because friends or presence services are missing"); | ||
410 | return new List<UUID>(); | ||
411 | } | ||
412 | |||
413 | List<UUID> localFriendsOnline = new List<UUID>(); | ||
414 | |||
415 | m_log.DebugFormat("[USER AGENT SERVICE]: Status notification: foreign user {0} wants to notify {1} local friends", foreignUserID, friends.Count); | ||
416 | |||
417 | // First, let's double check that the reported friends are, indeed, friends of that user | ||
418 | // And let's check that the secret matches | ||
419 | List<string> usersToBeNotified = new List<string>(); | ||
420 | foreach (string uui in friends) | ||
421 | { | ||
422 | UUID localUserID; | ||
423 | string secret = string.Empty, tmp = string.Empty; | ||
424 | if (Util.ParseUniversalUserIdentifier(uui, out localUserID, out tmp, out tmp, out tmp, out secret)) | ||
425 | { | ||
426 | FriendInfo[] friendInfos = m_FriendsService.GetFriends(localUserID); | ||
427 | foreach (FriendInfo finfo in friendInfos) | ||
428 | { | ||
429 | if (finfo.Friend.StartsWith(foreignUserID.ToString()) && finfo.Friend.EndsWith(secret)) | ||
430 | { | ||
431 | // great! | ||
432 | usersToBeNotified.Add(localUserID.ToString()); | ||
433 | } | ||
434 | } | ||
435 | } | ||
436 | } | ||
437 | |||
438 | // Now, let's send the notifications | ||
439 | m_log.DebugFormat("[USER AGENT SERVICE]: Status notification: user has {0} local friends", usersToBeNotified.Count); | ||
440 | |||
441 | // First, let's send notifications to local users who are online in the home grid | ||
442 | PresenceInfo[] friendSessions = m_PresenceService.GetAgents(usersToBeNotified.ToArray()); | ||
443 | if (friendSessions != null && friendSessions.Length > 0) | ||
444 | { | ||
445 | PresenceInfo friendSession = null; | ||
446 | foreach (PresenceInfo pinfo in friendSessions) | ||
447 | if (pinfo.RegionID != UUID.Zero) // let's guard against traveling agents | ||
448 | { | ||
449 | friendSession = pinfo; | ||
450 | break; | ||
451 | } | ||
452 | |||
453 | if (friendSession != null) | ||
454 | { | ||
455 | ForwardStatusNotificationToSim(friendSession.RegionID, foreignUserID, friendSession.UserID, online); | ||
456 | usersToBeNotified.Remove(friendSession.UserID.ToString()); | ||
457 | UUID id; | ||
458 | if (UUID.TryParse(friendSession.UserID, out id)) | ||
459 | localFriendsOnline.Add(id); | ||
460 | |||
461 | } | ||
462 | } | ||
463 | |||
464 | //// Lastly, let's notify the rest who may be online somewhere else | ||
465 | //foreach (string user in usersToBeNotified) | ||
466 | //{ | ||
467 | // UUID id = new UUID(user); | ||
468 | // if (m_Database.ContainsKey(id) && m_Database[id].GridExternalName != m_GridName) | ||
469 | // { | ||
470 | // string url = m_Database[id].GridExternalName; | ||
471 | // // forward | ||
472 | // m_log.WarnFormat("[USER AGENT SERVICE]: User {0} is visiting {1}. HG Status notifications still not implemented.", user, url); | ||
473 | // } | ||
474 | //} | ||
475 | |||
476 | // and finally, let's send the online friends | ||
477 | if (online) | ||
478 | { | ||
479 | return localFriendsOnline; | ||
480 | } | ||
481 | else | ||
482 | return new List<UUID>(); | ||
483 | } | ||
484 | |||
485 | [Obsolete] | ||
486 | protected void ForwardStatusNotificationToSim(UUID regionID, UUID foreignUserID, string user, bool online) | ||
487 | { | ||
488 | UUID userID; | ||
489 | if (UUID.TryParse(user, out userID)) | ||
490 | { | ||
491 | if (m_FriendsLocalSimConnector != null) | ||
492 | { | ||
493 | m_log.DebugFormat("[USER AGENT SERVICE]: Local Notify, user {0} is {1}", foreignUserID, (online ? "online" : "offline")); | ||
494 | m_FriendsLocalSimConnector.StatusNotify(foreignUserID, userID, online); | ||
495 | } | ||
496 | else | ||
497 | { | ||
498 | GridRegion region = m_GridService.GetRegionByUUID(UUID.Zero /* !!! */, regionID); | ||
499 | if (region != null) | ||
500 | { | ||
501 | m_log.DebugFormat("[USER AGENT SERVICE]: Remote Notify to region {0}, user {1} is {2}", region.RegionName, foreignUserID, (online ? "online" : "offline")); | ||
502 | m_FriendsSimConnector.StatusNotify(region, foreignUserID, userID.ToString(), online); | ||
503 | } | ||
504 | } | ||
505 | } | ||
506 | } | ||
507 | |||
508 | public List<UUID> GetOnlineFriends(UUID foreignUserID, List<string> friends) | ||
509 | { | ||
510 | List<UUID> online = new List<UUID>(); | ||
511 | |||
512 | if (m_FriendsService == null || m_PresenceService == null) | ||
513 | { | ||
514 | m_log.WarnFormat("[USER AGENT SERVICE]: Unable to get online friends because friends or presence services are missing"); | ||
515 | return online; | ||
516 | } | ||
517 | |||
518 | m_log.DebugFormat("[USER AGENT SERVICE]: Foreign user {0} wants to know status of {1} local friends", foreignUserID, friends.Count); | ||
519 | |||
520 | // First, let's double check that the reported friends are, indeed, friends of that user | ||
521 | // And let's check that the secret matches and the rights | ||
522 | List<string> usersToBeNotified = new List<string>(); | ||
523 | foreach (string uui in friends) | ||
524 | { | ||
525 | UUID localUserID; | ||
526 | string secret = string.Empty, tmp = string.Empty; | ||
527 | if (Util.ParseUniversalUserIdentifier(uui, out localUserID, out tmp, out tmp, out tmp, out secret)) | ||
528 | { | ||
529 | FriendInfo[] friendInfos = m_FriendsService.GetFriends(localUserID); | ||
530 | foreach (FriendInfo finfo in friendInfos) | ||
531 | { | ||
532 | if (finfo.Friend.StartsWith(foreignUserID.ToString()) && finfo.Friend.EndsWith(secret) && | ||
533 | (finfo.TheirFlags & (int)FriendRights.CanSeeOnline) != 0 && (finfo.TheirFlags != -1)) | ||
534 | { | ||
535 | // great! | ||
536 | usersToBeNotified.Add(localUserID.ToString()); | ||
537 | } | ||
538 | } | ||
539 | } | ||
540 | } | ||
541 | |||
542 | // Now, let's find out their status | ||
543 | m_log.DebugFormat("[USER AGENT SERVICE]: GetOnlineFriends: user has {0} local friends with status rights", usersToBeNotified.Count); | ||
544 | |||
545 | // First, let's send notifications to local users who are online in the home grid | ||
546 | PresenceInfo[] friendSessions = m_PresenceService.GetAgents(usersToBeNotified.ToArray()); | ||
547 | if (friendSessions != null && friendSessions.Length > 0) | ||
548 | { | ||
549 | foreach (PresenceInfo pi in friendSessions) | ||
550 | { | ||
551 | UUID presenceID; | ||
552 | if (UUID.TryParse(pi.UserID, out presenceID)) | ||
553 | online.Add(presenceID); | ||
554 | } | ||
555 | } | ||
556 | |||
557 | return online; | ||
558 | } | ||
559 | |||
560 | public Dictionary<string, object> GetUserInfo(UUID userID) | ||
561 | { | ||
562 | Dictionary<string, object> info = new Dictionary<string, object>(); | ||
563 | |||
564 | if (m_UserAccountService == null) | ||
565 | { | ||
566 | m_log.WarnFormat("[USER AGENT SERVICE]: Unable to get user flags because user account service is missing"); | ||
567 | info["result"] = "fail"; | ||
568 | info["message"] = "UserAccountService is missing!"; | ||
569 | return info; | ||
570 | } | ||
571 | |||
572 | UserAccount account = m_UserAccountService.GetUserAccount(UUID.Zero /*!!!*/, userID); | ||
573 | |||
574 | if (account != null) | ||
575 | { | ||
576 | info.Add("user_firstname", account.FirstName); | ||
577 | info.Add("user_lastname", account.LastName); | ||
578 | info.Add("result", "success"); | ||
579 | |||
580 | if (m_ShowDetails) | ||
581 | { | ||
582 | info.Add("user_flags", account.UserFlags); | ||
583 | info.Add("user_created", account.Created); | ||
584 | info.Add("user_title", account.UserTitle); | ||
585 | } | ||
586 | else | ||
587 | { | ||
588 | info.Add("user_flags", 0); | ||
589 | info.Add("user_created", 0); | ||
590 | info.Add("user_title", string.Empty); | ||
591 | } | ||
592 | } | ||
593 | |||
594 | return info; | ||
595 | } | ||
596 | |||
597 | public Dictionary<string, object> GetServerURLs(UUID userID) | ||
598 | { | ||
599 | if (m_UserAccountService == null) | ||
600 | { | ||
601 | m_log.WarnFormat("[USER AGENT SERVICE]: Unable to get server URLs because user account service is missing"); | ||
602 | return new Dictionary<string, object>(); | ||
603 | } | ||
604 | UserAccount account = m_UserAccountService.GetUserAccount(UUID.Zero /*!!!*/, userID); | ||
605 | if (account != null) | ||
606 | return account.ServiceURLs; | ||
607 | |||
608 | return new Dictionary<string, object>(); | ||
609 | } | ||
610 | |||
611 | public string LocateUser(UUID userID) | ||
612 | { | ||
613 | HGTravelingData[] hgts = m_Database.GetSessions(userID); | ||
614 | if (hgts == null) | ||
615 | return string.Empty; | ||
616 | |||
617 | foreach (HGTravelingData t in hgts) | ||
618 | if (t.Data.ContainsKey("GridExternalName") && !m_GridName.Equals(t.Data["GridExternalName"])) | ||
619 | return t.Data["GridExternalName"]; | ||
620 | |||
621 | return string.Empty; | ||
622 | } | ||
623 | |||
624 | public string GetUUI(UUID userID, UUID targetUserID) | ||
625 | { | ||
626 | // Let's see if it's a local user | ||
627 | UserAccount account = m_UserAccountService.GetUserAccount(UUID.Zero, targetUserID); | ||
628 | if (account != null) | ||
629 | return targetUserID.ToString() + ";" + m_GridName + ";" + account.FirstName + " " + account.LastName ; | ||
630 | |||
631 | // Let's try the list of friends | ||
632 | FriendInfo[] friends = m_FriendsService.GetFriends(userID); | ||
633 | if (friends != null && friends.Length > 0) | ||
634 | { | ||
635 | foreach (FriendInfo f in friends) | ||
636 | if (f.Friend.StartsWith(targetUserID.ToString())) | ||
637 | { | ||
638 | // Let's remove the secret | ||
639 | UUID id; string tmp = string.Empty, secret = string.Empty; | ||
640 | if (Util.ParseUniversalUserIdentifier(f.Friend, out id, out tmp, out tmp, out tmp, out secret)) | ||
641 | return f.Friend.Replace(secret, "0"); | ||
642 | } | ||
643 | } | ||
644 | |||
645 | return string.Empty; | ||
646 | } | ||
647 | |||
648 | public UUID GetUUID(String first, String last) | ||
649 | { | ||
650 | // Let's see if it's a local user | ||
651 | UserAccount account = m_UserAccountService.GetUserAccount(UUID.Zero, first, last); | ||
652 | if (account != null) | ||
653 | { | ||
654 | // check user level | ||
655 | if (account.UserLevel < m_LevelOutsideContacts) | ||
656 | return UUID.Zero; | ||
657 | else | ||
658 | return account.PrincipalID; | ||
659 | } | ||
660 | else | ||
661 | return UUID.Zero; | ||
662 | } | ||
663 | |||
664 | #region Misc | ||
665 | |||
666 | private bool IsException(string dest, int level, Dictionary<int, List<string>> exceptions) | ||
667 | { | ||
668 | if (!exceptions.ContainsKey(level)) | ||
669 | return false; | ||
670 | |||
671 | bool exception = false; | ||
672 | if (exceptions[level].Count > 0) // we have exceptions | ||
673 | { | ||
674 | string destination = dest; | ||
675 | if (!destination.EndsWith("/")) | ||
676 | destination += "/"; | ||
677 | |||
678 | if (exceptions[level].Find(delegate(string s) | ||
679 | { | ||
680 | if (!s.EndsWith("/")) | ||
681 | s += "/"; | ||
682 | return s == destination; | ||
683 | }) != null) | ||
684 | exception = true; | ||
685 | } | ||
686 | |||
687 | return exception; | ||
688 | } | ||
689 | |||
690 | private void StoreTravelInfo(TravelingAgentInfo travel) | ||
691 | { | ||
692 | if (travel == null) | ||
693 | return; | ||
694 | |||
695 | HGTravelingData hgt = new HGTravelingData(); | ||
696 | hgt.SessionID = travel.SessionID; | ||
697 | hgt.UserID = travel.UserID; | ||
698 | hgt.Data = new Dictionary<string, string>(); | ||
699 | hgt.Data["GridExternalName"] = travel.GridExternalName; | ||
700 | hgt.Data["ServiceToken"] = travel.ServiceToken; | ||
701 | hgt.Data["ClientIPAddress"] = travel.ClientIPAddress; | ||
702 | hgt.Data["MyIPAddress"] = travel.MyIpAddress; | ||
703 | |||
704 | m_Database.Store(hgt); | ||
705 | } | ||
706 | #endregion | ||
707 | |||
708 | } | ||
709 | |||
710 | class TravelingAgentInfo | ||
711 | { | ||
712 | public UUID SessionID; | ||
713 | public UUID UserID; | ||
714 | public string GridExternalName = string.Empty; | ||
715 | public string ServiceToken = string.Empty; | ||
716 | public string ClientIPAddress = string.Empty; // as seen from this user agent service | ||
717 | public string MyIpAddress = string.Empty; // the user agent service's external IP, as seen from the next gatekeeper | ||
718 | |||
719 | public TravelingAgentInfo(HGTravelingData t) | ||
720 | { | ||
721 | if (t.Data != null) | ||
722 | { | ||
723 | SessionID = new UUID(t.SessionID); | ||
724 | UserID = new UUID(t.UserID); | ||
725 | GridExternalName = t.Data["GridExternalName"]; | ||
726 | ServiceToken = t.Data["ServiceToken"]; | ||
727 | ClientIPAddress = t.Data["ClientIPAddress"]; | ||
728 | MyIpAddress = t.Data["MyIPAddress"]; | ||
729 | } | ||
730 | } | ||
731 | |||
732 | public TravelingAgentInfo(TravelingAgentInfo old) | ||
733 | { | ||
734 | if (old != null) | ||
735 | { | ||
736 | SessionID = old.SessionID; | ||
737 | UserID = old.UserID; | ||
738 | GridExternalName = old.GridExternalName; | ||
739 | ServiceToken = old.ServiceToken; | ||
740 | ClientIPAddress = old.ClientIPAddress; | ||
741 | MyIpAddress = old.MyIpAddress; | ||
742 | } | ||
743 | } | ||
744 | } | ||
745 | |||
746 | } | ||
diff --git a/OpenSim/Services/HypergridService/UserAgentServiceBase.cs b/OpenSim/Services/HypergridService/UserAgentServiceBase.cs new file mode 100644 index 0000000..a00e5a6 --- /dev/null +++ b/OpenSim/Services/HypergridService/UserAgentServiceBase.cs | |||
@@ -0,0 +1,84 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Reflection; | ||
30 | using Nini.Config; | ||
31 | using OpenSim.Framework; | ||
32 | using OpenSim.Data; | ||
33 | using OpenSim.Services.Interfaces; | ||
34 | using OpenSim.Services.Base; | ||
35 | |||
36 | namespace OpenSim.Services.HypergridService | ||
37 | { | ||
38 | public class UserAgentServiceBase : ServiceBase | ||
39 | { | ||
40 | protected IHGTravelingData m_Database = null; | ||
41 | |||
42 | public UserAgentServiceBase(IConfigSource config) | ||
43 | : base(config) | ||
44 | { | ||
45 | string dllName = String.Empty; | ||
46 | string connString = String.Empty; | ||
47 | string realm = "hg_traveling_data"; | ||
48 | |||
49 | // | ||
50 | // Try reading the [DatabaseService] section, if it exists | ||
51 | // | ||
52 | IConfig dbConfig = config.Configs["DatabaseService"]; | ||
53 | if (dbConfig != null) | ||
54 | { | ||
55 | if (dllName == String.Empty) | ||
56 | dllName = dbConfig.GetString("StorageProvider", String.Empty); | ||
57 | if (connString == String.Empty) | ||
58 | connString = dbConfig.GetString("ConnectionString", String.Empty); | ||
59 | } | ||
60 | |||
61 | // | ||
62 | // [UserAgentService] section overrides [DatabaseService], if it exists | ||
63 | // | ||
64 | IConfig gridConfig = config.Configs["UserAgentService"]; | ||
65 | if (gridConfig != null) | ||
66 | { | ||
67 | dllName = gridConfig.GetString("StorageProvider", dllName); | ||
68 | connString = gridConfig.GetString("ConnectionString", connString); | ||
69 | realm = gridConfig.GetString("Realm", realm); | ||
70 | } | ||
71 | |||
72 | // | ||
73 | // We tried, but this doesn't exist. We can't proceed. | ||
74 | // | ||
75 | if (dllName.Equals(String.Empty)) | ||
76 | throw new Exception("No StorageProvider configured"); | ||
77 | |||
78 | m_Database = LoadPlugin<IHGTravelingData>(dllName, new Object[] { connString, realm }); | ||
79 | if (m_Database == null) | ||
80 | throw new Exception("Could not find a storage interface in the given module"); | ||
81 | |||
82 | } | ||
83 | } | ||
84 | } | ||