diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/HGMessageTransferModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/HGMessageTransferModule.cs new file mode 100644 index 0000000000..9ba4e4953d --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/HGMessageTransferModule.cs @@ -0,0 +1,268 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using Mono.Addins; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo; +using OpenSim.Services.Interfaces; +using OpenSim.Services.Connectors.InstantMessage; +using OpenSim.Services.Connectors.Hypergrid; +using OpenSim.Server.Handlers.Hypergrid; + +namespace OpenSim.Region.CoreModules.Avatar.InstantMessage +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")] + public class HGMessageTransferModule : ISharedRegionModule, IMessageTransferModule, IInstantMessageSimConnector + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected bool m_Enabled = false; + protected List m_Scenes = new List(); + + protected IInstantMessage m_IMService; + protected Dictionary m_UserLocationMap = new Dictionary(); + + public event UndeliveredMessage OnUndeliveredMessage; + + IUserManagement m_uMan; + IUserManagement UserManagementModule + { + get + { + if (m_uMan == null) + m_uMan = m_Scenes[0].RequestModuleInterface(); + return m_uMan; + } + } + + public virtual void Initialise(IConfigSource config) + { + IConfig cnf = config.Configs["Messaging"]; + if (cnf != null && cnf.GetString( + "MessageTransferModule", "MessageTransferModule") != Name) + { + m_log.Debug("[HG MESSAGE TRANSFER]: Disabled by configuration"); + return; + } + + InstantMessageServerConnector imServer = new InstantMessageServerConnector(config, MainServer.Instance, this); + m_IMService = imServer.GetService(); + m_Enabled = true; + } + + public virtual void AddRegion(Scene scene) + { + if (!m_Enabled) + return; + + lock (m_Scenes) + { + m_log.DebugFormat("[HG MESSAGE TRANSFER]: Message transfer module {0} active", Name); + scene.RegisterModuleInterface(this); + m_Scenes.Add(scene); + } + } + + public virtual void PostInitialise() + { + if (!m_Enabled) + return; + + } + + public virtual void RegionLoaded(Scene scene) + { + } + + public virtual void RemoveRegion(Scene scene) + { + if (!m_Enabled) + return; + + lock (m_Scenes) + { + m_Scenes.Remove(scene); + } + } + + public virtual void Close() + { + } + + public virtual string Name + { + get { return "HGMessageTransferModule"; } + } + + public virtual Type ReplaceableInterface + { + get { return null; } + } + + public void SendInstantMessage(GridInstantMessage im, MessageResultNotification result) + { + UUID toAgentID = new UUID(im.toAgentID); + + // Try root avatar only first + foreach (Scene scene in m_Scenes) + { + if (scene.Entities.ContainsKey(toAgentID) && + scene.Entities[toAgentID] is ScenePresence) + { +// m_log.DebugFormat( +// "[INSTANT MESSAGE]: Looking for root agent {0} in {1}", +// toAgentID.ToString(), scene.RegionInfo.RegionName); + + ScenePresence user = (ScenePresence) scene.Entities[toAgentID]; + if (!user.IsChildAgent) + { + // Local message +// m_log.DebugFormat("[INSTANT MESSAGE]: Delivering IM to root agent {0} {1}", user.Name, toAgentID); + user.ControllingClient.SendInstantMessage(im); + + // Message sent + result(true); + return; + } + } + } + + // try child avatar second + foreach (Scene scene in m_Scenes) + { +// m_log.DebugFormat( +// "[INSTANT MESSAGE]: Looking for child of {0} in {1}", toAgentID, scene.RegionInfo.RegionName); + + if (scene.Entities.ContainsKey(toAgentID) && + scene.Entities[toAgentID] is ScenePresence) + { + // Local message + ScenePresence user = (ScenePresence) scene.Entities[toAgentID]; + +// m_log.DebugFormat("[INSTANT MESSAGE]: Delivering IM to child agent {0} {1}", user.Name, toAgentID); + user.ControllingClient.SendInstantMessage(im); + + // Message sent + result(true); + return; + } + } + +// m_log.DebugFormat("[INSTANT MESSAGE]: Delivering IM to {0} via XMLRPC", im.toAgentID); + // Is the user a local user? + UserAccount account = m_Scenes[0].UserAccountService.GetUserAccount(m_Scenes[0].RegionInfo.ScopeID, toAgentID); + string url = string.Empty; + PresenceInfo upd; + if (account == null) // foreign user + url = UserManagementModule.GetUserServerURL(toAgentID, "IMServerURI"); + + Util.FireAndForget(delegate + { + bool success = m_IMService.OutgoingInstantMessage(im, url); + result(success); + }); + + return; + } + + protected bool SendIMToScene(GridInstantMessage gim, UUID toAgentID) + { + bool successful = false; + foreach (Scene scene in m_Scenes) + { + if (scene.Entities.ContainsKey(toAgentID) && + scene.Entities[toAgentID] is ScenePresence) + { + ScenePresence user = + (ScenePresence)scene.Entities[toAgentID]; + + if (!user.IsChildAgent) + { + scene.EventManager.TriggerIncomingInstantMessage(gim); + successful = true; + } + } + } + if (!successful) + { + // If the message can't be delivered to an agent, it + // is likely to be a group IM. On a group IM, the + // imSessionID = toAgentID = group id. Raise the + // unhandled IM event to give the groups module + // a chance to pick it up. We raise that in a random + // scene, since the groups module is shared. + // + m_Scenes[0].EventManager.TriggerUnhandledInstantMessage(gim); + } + + return successful; + } + + protected void HandleUndeliveredMessage(GridInstantMessage im, MessageResultNotification result) + { + UndeliveredMessage handlerUndeliveredMessage = OnUndeliveredMessage; + + // If this event has handlers, then an IM from an agent will be + // considered delivered. This will suppress the error message. + // + if (handlerUndeliveredMessage != null) + { + handlerUndeliveredMessage(im); + if (im.dialog == (byte)InstantMessageDialog.MessageFromAgent) + result(true); + else + result(false); + return; + } + + //m_log.DebugFormat("[INSTANT MESSAGE]: Undeliverable"); + result(false); + } + + #region IInstantMessageSimConnector + public bool SendInstantMessage(GridInstantMessage im) + { + //m_log.DebugFormat("[XXX] Hook SendInstantMessage {0}", im.message); + UUID agentID = new UUID(im.toAgentID); + return SendIMToScene(im, agentID); + } + #endregion + + + } +} diff --git a/OpenSim/Server/Handlers/Hypergrid/InstantMessageServerConnector.cs b/OpenSim/Server/Handlers/Hypergrid/InstantMessageServerConnector.cs new file mode 100644 index 0000000000..138313aedd --- /dev/null +++ b/OpenSim/Server/Handlers/Hypergrid/InstantMessageServerConnector.cs @@ -0,0 +1,280 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Reflection; + +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Server.Base; +using OpenSim.Services.Interfaces; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Server.Handlers.Base; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; + +using log4net; +using Nwc.XmlRpc; +using OpenMetaverse; + +namespace OpenSim.Server.Handlers.Hypergrid +{ + public class InstantMessageServerConnector : ServiceConnector + { + private static readonly ILog m_log = + LogManager.GetLogger( + MethodBase.GetCurrentMethod().DeclaringType); + + private IInstantMessage m_IMService; + + public InstantMessageServerConnector(IConfigSource config, IHttpServer server) : + this(config, server, null) + { + } + + public InstantMessageServerConnector(IConfigSource config, IHttpServer server, IInstantMessageSimConnector simConnector) : + base(config, server, String.Empty) + { + IConfig gridConfig = config.Configs["HGInstantMessageService"]; + if (gridConfig != null) + { + string serviceDll = gridConfig.GetString("LocalServiceModule", string.Empty); + + Object[] args = new Object[] { config, simConnector }; + m_IMService = ServerUtils.LoadPlugin(serviceDll, args); + } + if (m_IMService == null) + throw new Exception("InstantMessage server connector cannot proceed because of missing service"); + + MainServer.Instance.AddXmlRPCHandler("grid_instant_message", ProcessInstantMessage, false); + + } + + public IInstantMessage GetService() + { + return m_IMService; + } + + protected virtual XmlRpcResponse ProcessInstantMessage(XmlRpcRequest request, IPEndPoint remoteClient) + { + bool successful = false; + + try + { + // various rational defaults + UUID fromAgentID = UUID.Zero; + UUID toAgentID = UUID.Zero; + UUID imSessionID = UUID.Zero; + uint timestamp = 0; + string fromAgentName = ""; + string message = ""; + byte dialog = (byte)0; + bool fromGroup = false; + byte offline = (byte)0; + uint ParentEstateID = 0; + Vector3 Position = Vector3.Zero; + UUID RegionID = UUID.Zero; + byte[] binaryBucket = new byte[0]; + + float pos_x = 0; + float pos_y = 0; + float pos_z = 0; + //m_log.Info("Processing IM"); + + + Hashtable requestData = (Hashtable)request.Params[0]; + // Check if it's got all the data + if (requestData.ContainsKey("from_agent_id") + && requestData.ContainsKey("to_agent_id") && requestData.ContainsKey("im_session_id") + && requestData.ContainsKey("timestamp") && requestData.ContainsKey("from_agent_name") + && requestData.ContainsKey("message") && requestData.ContainsKey("dialog") + && requestData.ContainsKey("from_group") + && requestData.ContainsKey("offline") && requestData.ContainsKey("parent_estate_id") + && requestData.ContainsKey("position_x") && requestData.ContainsKey("position_y") + && requestData.ContainsKey("position_z") && requestData.ContainsKey("region_id") + && requestData.ContainsKey("binary_bucket")) + { + // Do the easy way of validating the UUIDs + UUID.TryParse((string)requestData["from_agent_id"], out fromAgentID); + UUID.TryParse((string)requestData["to_agent_id"], out toAgentID); + UUID.TryParse((string)requestData["im_session_id"], out imSessionID); + UUID.TryParse((string)requestData["region_id"], out RegionID); + + try + { + timestamp = (uint)Convert.ToInt32((string)requestData["timestamp"]); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } + + fromAgentName = (string)requestData["from_agent_name"]; + message = (string)requestData["message"]; + if (message == null) + message = string.Empty; + + // Bytes don't transfer well over XMLRPC, so, we Base64 Encode them. + string requestData1 = (string)requestData["dialog"]; + if (string.IsNullOrEmpty(requestData1)) + { + dialog = 0; + } + else + { + byte[] dialogdata = Convert.FromBase64String(requestData1); + dialog = dialogdata[0]; + } + + if ((string)requestData["from_group"] == "TRUE") + fromGroup = true; + + string requestData2 = (string)requestData["offline"]; + if (String.IsNullOrEmpty(requestData2)) + { + offline = 0; + } + else + { + byte[] offlinedata = Convert.FromBase64String(requestData2); + offline = offlinedata[0]; + } + + try + { + ParentEstateID = (uint)Convert.ToInt32((string)requestData["parent_estate_id"]); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } + + try + { + pos_x = (uint)Convert.ToInt32((string)requestData["position_x"]); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } + try + { + pos_y = (uint)Convert.ToInt32((string)requestData["position_y"]); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } + try + { + pos_z = (uint)Convert.ToInt32((string)requestData["position_z"]); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } + + Position = new Vector3(pos_x, pos_y, pos_z); + + string requestData3 = (string)requestData["binary_bucket"]; + if (string.IsNullOrEmpty(requestData3)) + { + binaryBucket = new byte[0]; + } + else + { + binaryBucket = Convert.FromBase64String(requestData3); + } + + // Create a New GridInstantMessageObject the the data + GridInstantMessage gim = new GridInstantMessage(); + gim.fromAgentID = fromAgentID.Guid; + gim.fromAgentName = fromAgentName; + gim.fromGroup = fromGroup; + gim.imSessionID = imSessionID.Guid; + gim.RegionID = UUID.Zero.Guid; // RegionID.Guid; + gim.timestamp = timestamp; + gim.toAgentID = toAgentID.Guid; + gim.message = message; + gim.dialog = dialog; + gim.offline = offline; + gim.ParentEstateID = ParentEstateID; + gim.Position = Position; + gim.binaryBucket = binaryBucket; + + successful = m_IMService.IncomingInstantMessage(gim); + + } + } + catch (Exception e) + { + m_log.Error("[INSTANT MESSAGE]: Caught unexpected exception:", e); + successful = false; + } + + //Send response back to region calling if it was successful + // calling region uses this to know when to look up a user's location again. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable respdata = new Hashtable(); + if (successful) + respdata["success"] = "TRUE"; + else + respdata["success"] = "FALSE"; + resp.Value = respdata; + + return resp; + } + + } +} diff --git a/OpenSim/Server/Handlers/Hypergrid/UserAgentServerConnector.cs b/OpenSim/Server/Handlers/Hypergrid/UserAgentServerConnector.cs index e51fe0bc26..942d96073b 100644 --- a/OpenSim/Server/Handlers/Hypergrid/UserAgentServerConnector.cs +++ b/OpenSim/Server/Handlers/Hypergrid/UserAgentServerConnector.cs @@ -52,6 +52,7 @@ namespace OpenSim.Server.Handlers.Hypergrid // MethodBase.GetCurrentMethod().DeclaringType); private IUserAgentService m_HomeUsersService; + private string[] m_AuthorizedCallers; public UserAgentServerConnector(IConfigSource config, IHttpServer server) : this(config, server, null) @@ -75,6 +76,10 @@ namespace OpenSim.Server.Handlers.Hypergrid string loginServerIP = gridConfig.GetString("LoginServerIP", "127.0.0.1"); bool proxy = gridConfig.GetBoolean("HasProxy", false); + string csv = gridConfig.GetString("AuthorizedCallers", "127.0.0.1"); + csv = csv.Replace(" ", ""); + m_AuthorizedCallers = csv.Split(','); + server.AddXmlRPCHandler("agent_is_coming_home", AgentIsComingHome, false); server.AddXmlRPCHandler("get_home_region", GetHomeRegion, false); server.AddXmlRPCHandler("verify_agent", VerifyAgent, false); @@ -85,6 +90,8 @@ namespace OpenSim.Server.Handlers.Hypergrid server.AddXmlRPCHandler("get_online_friends", GetOnlineFriends, false); server.AddXmlRPCHandler("get_server_urls", GetServerURLs, false); + server.AddXmlRPCHandler("locate_user", LocateUser, false); + server.AddHTTPHandler("/homeagent/", new HomeAgentHandler(m_HomeUsersService, loginServerIP, proxy).Handler); } @@ -306,5 +313,46 @@ namespace OpenSim.Server.Handlers.Hypergrid } + /// + /// Locates the user. + /// This is a sensitive operation, only authorized IP addresses can perform it. + /// + /// + /// + /// + public XmlRpcResponse LocateUser(XmlRpcRequest request, IPEndPoint remoteClient) + { + Hashtable hash = new Hashtable(); + + bool authorized = false; + foreach (string s in m_AuthorizedCallers) + if (s == remoteClient.Address.ToString()) + { + authorized = true; + break; + } + + if (authorized) + { + Hashtable requestData = (Hashtable)request.Params[0]; + //string host = (string)requestData["host"]; + //string portstr = (string)requestData["port"]; + if (requestData.ContainsKey("userID")) + { + string userID_str = (string)requestData["userID"]; + UUID userID = UUID.Zero; + UUID.TryParse(userID_str, out userID); + + string url = m_HomeUsersService.LocateUser(userID); + if (url != string.Empty) + hash["URL"] = url; + } + } + + XmlRpcResponse response = new XmlRpcResponse(); + response.Value = hash; + return response; + + } } } diff --git a/OpenSim/Services/Connectors/Hypergrid/UserAgentServiceConnector.cs b/OpenSim/Services/Connectors/Hypergrid/UserAgentServiceConnector.cs index 265bacf33e..50036b36e8 100644 --- a/OpenSim/Services/Connectors/Hypergrid/UserAgentServiceConnector.cs +++ b/OpenSim/Services/Connectors/Hypergrid/UserAgentServiceConnector.cs @@ -560,6 +560,64 @@ namespace OpenSim.Services.Connectors.Hypergrid return serverURLs; } + public string LocateUser(UUID userID) + { + Hashtable hash = new Hashtable(); + hash["userID"] = userID.ToString(); + + IList paramList = new ArrayList(); + paramList.Add(hash); + + XmlRpcRequest request = new XmlRpcRequest("locate_user", paramList); + string reason = string.Empty; + + // Send and get reply + string url = string.Empty; + XmlRpcResponse response = null; + try + { + response = request.Send(m_ServerURL, 10000); + } + catch (Exception e) + { + m_log.DebugFormat("[USER AGENT CONNECTOR]: Unable to contact remote server {0}", m_ServerURL); + reason = "Exception: " + e.Message; + return url; + } + + if (response.IsFault) + { + m_log.ErrorFormat("[USER AGENT CONNECTOR]: remote call to {0} returned an error: {1}", m_ServerURL, response.FaultString); + reason = "XMLRPC Fault"; + return url; + } + + hash = (Hashtable)response.Value; + //foreach (Object o in hash) + // m_log.Debug(">> " + ((DictionaryEntry)o).Key + ":" + ((DictionaryEntry)o).Value); + try + { + if (hash == null) + { + m_log.ErrorFormat("[USER AGENT CONNECTOR]: LocateUser Got null response from {0}! THIS IS BAAAAD", m_ServerURL); + reason = "Internal error 1"; + return url; + } + + // Here's the actual response + if (hash.ContainsKey("URL")) + url = hash["URL"].ToString(); + + } + catch (Exception e) + { + m_log.ErrorFormat("[USER AGENT CONNECTOR]: Got exception on LocateUser response."); + reason = "Exception: " + e.Message; + } + + return url; + } + private bool GetBoolResponse(XmlRpcRequest request, out string reason) { //m_log.Debug("[USER AGENT CONNECTOR]: GetBoolResponse from/to " + m_ServerURL); diff --git a/OpenSim/Services/Connectors/InstantMessage/InstantMessageServiceConnector.cs b/OpenSim/Services/Connectors/InstantMessage/InstantMessageServiceConnector.cs new file mode 100644 index 0000000000..65ee7c7aeb --- /dev/null +++ b/OpenSim/Services/Connectors/InstantMessage/InstantMessageServiceConnector.cs @@ -0,0 +1,129 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Reflection; + +using OpenMetaverse; +using Nwc.XmlRpc; +using log4net; + +using OpenSim.Framework; + +namespace OpenSim.Services.Connectors.InstantMessage +{ + public class InstantMessageServiceConnector + { + private static readonly ILog m_log = + LogManager.GetLogger( + MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// This actually does the XMLRPC Request + /// + /// URL we pull the data out of to send the request to + /// The Instant Message + /// Bool if the message was successfully delivered at the other side. + public static bool SendInstantMessage(string url, GridInstantMessage im) + { + Hashtable xmlrpcdata = ConvertGridInstantMessageToXMLRPC(im); + xmlrpcdata["region_handle"] = 0; + + ArrayList SendParams = new ArrayList(); + SendParams.Add(xmlrpcdata); + XmlRpcRequest GridReq = new XmlRpcRequest("grid_instant_message", SendParams); + try + { + + XmlRpcResponse GridResp = GridReq.Send(url, 3000); + + Hashtable responseData = (Hashtable)GridResp.Value; + + if (responseData.ContainsKey("success")) + { + if ((string)responseData["success"] == "TRUE") + { + m_log.DebugFormat("[XXX] Success"); + return true; + } + else + { + m_log.DebugFormat("[XXX] Fail"); + return false; + } + } + else + { + return false; + } + } + catch (WebException e) + { + m_log.ErrorFormat("[GRID INSTANT MESSAGE]: Error sending message to {0} the host didn't respond " + e.ToString(), url); + } + + return false; + } + + /// + /// Takes a GridInstantMessage and converts it into a Hashtable for XMLRPC + /// + /// The GridInstantMessage object + /// Hashtable containing the XMLRPC request + protected static Hashtable ConvertGridInstantMessageToXMLRPC(GridInstantMessage msg) + { + Hashtable gim = new Hashtable(); + gim["from_agent_id"] = msg.fromAgentID.ToString(); + // Kept for compatibility + gim["from_agent_session"] = UUID.Zero.ToString(); + gim["to_agent_id"] = msg.toAgentID.ToString(); + gim["im_session_id"] = msg.imSessionID.ToString(); + gim["timestamp"] = msg.timestamp.ToString(); + gim["from_agent_name"] = msg.fromAgentName; + gim["message"] = msg.message; + byte[] dialogdata = new byte[1]; dialogdata[0] = msg.dialog; + gim["dialog"] = Convert.ToBase64String(dialogdata, Base64FormattingOptions.None); + + if (msg.fromGroup) + gim["from_group"] = "TRUE"; + else + gim["from_group"] = "FALSE"; + byte[] offlinedata = new byte[1]; offlinedata[0] = msg.offline; + gim["offline"] = Convert.ToBase64String(offlinedata, Base64FormattingOptions.None); + gim["parent_estate_id"] = msg.ParentEstateID.ToString(); + gim["position_x"] = msg.Position.X.ToString(); + gim["position_y"] = msg.Position.Y.ToString(); + gim["position_z"] = msg.Position.Z.ToString(); + gim["region_id"] = msg.RegionID.ToString(); + gim["binary_bucket"] = Convert.ToBase64String(msg.binaryBucket, Base64FormattingOptions.None); + return gim; + } + + } +} diff --git a/OpenSim/Services/HypergridService/HGInstantMessageService.cs b/OpenSim/Services/HypergridService/HGInstantMessageService.cs new file mode 100644 index 0000000000..6178ca1d7b --- /dev/null +++ b/OpenSim/Services/HypergridService/HGInstantMessageService.cs @@ -0,0 +1,311 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; + +using OpenSim.Framework; +using OpenSim.Services.Connectors.Friends; +using OpenSim.Services.Connectors.Hypergrid; +using OpenSim.Services.Interfaces; +using OpenSim.Services.Connectors.InstantMessage; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using OpenSim.Server.Base; +using FriendInfo = OpenSim.Services.Interfaces.FriendInfo; + +using OpenMetaverse; +using log4net; +using Nini.Config; + +namespace OpenSim.Services.HypergridService +{ + /// + /// Inter-grid IM + /// + public class HGInstantMessageService : IInstantMessage + { + private static readonly ILog m_log = + LogManager.GetLogger( + MethodBase.GetCurrentMethod().DeclaringType); + + private const double CACHE_EXPIRATION_SECONDS = 120000.0; // 33 hours + + static bool m_Initialized = false; + + protected static IGridService m_GridService; + protected static IPresenceService m_PresenceService; + protected static IUserAgentService m_UserAgentService; + + protected static IInstantMessageSimConnector m_IMSimConnector; + + protected Dictionary m_UserLocationMap = new Dictionary(); + private ExpiringCache m_RegionCache; + + public HGInstantMessageService(IConfigSource config) + : this(config, null) + { + } + + public HGInstantMessageService(IConfigSource config, IInstantMessageSimConnector imConnector) + { + if (imConnector != null) + m_IMSimConnector = imConnector; + + if (!m_Initialized) + { + m_Initialized = true; + + m_log.DebugFormat("[HG IM SERVICE]: Starting..."); + + IConfig serverConfig = config.Configs["HGInstantMessageService"]; + if (serverConfig == null) + throw new Exception(String.Format("No section HGInstantMessageService in config file")); + + string gridService = serverConfig.GetString("GridService", String.Empty); + string presenceService = serverConfig.GetString("PresenceService", String.Empty); + string userAgentService = serverConfig.GetString("UserAgentService", String.Empty); + + if (gridService == string.Empty || presenceService == string.Empty) + throw new Exception(String.Format("Incomplete specifications, InstantMessage Service cannot function.")); + + Object[] args = new Object[] { config }; + m_GridService = ServerUtils.LoadPlugin(gridService, args); + m_PresenceService = ServerUtils.LoadPlugin(presenceService, args); + m_UserAgentService = ServerUtils.LoadPlugin(userAgentService, args); + + m_RegionCache = new ExpiringCache(); + + } + } + + public bool IncomingInstantMessage(GridInstantMessage im) + { + m_log.DebugFormat("[HG IM SERVICE]: Received message {0} from {1} to {2}", im.message, im.fromAgentID, im.toAgentID); + UUID toAgentID = new UUID(im.toAgentID); + + if (m_IMSimConnector != null) + { + m_log.DebugFormat("[XXX] SendIMToRegion local im connector"); + return m_IMSimConnector.SendInstantMessage(im); + } + else + return TrySendInstantMessage(im, "", true); + } + + public bool OutgoingInstantMessage(GridInstantMessage im, string url) + { + m_log.DebugFormat("[HG IM SERVICE]: Sending message {0} from {1} to {2}@{3}", im.message, im.fromAgentID, im.toAgentID, url); + if (url != string.Empty) + return TrySendInstantMessage(im, url, true); + else + { + PresenceInfo upd = new PresenceInfo(); + upd.RegionID = UUID.Zero; + return TrySendInstantMessage(im, upd, true); + } + } + + protected bool TrySendInstantMessage(GridInstantMessage im, object previousLocation, bool firstTime) + { + UUID toAgentID = new UUID(im.toAgentID); + + PresenceInfo upd = null; + string url = string.Empty; + + bool lookupAgent = false; + + lock (m_UserLocationMap) + { + if (m_UserLocationMap.ContainsKey(toAgentID)) + { + object o = m_UserLocationMap[toAgentID]; + if (o is PresenceInfo) + upd = (PresenceInfo)o; + else if (o is string) + url = (string)o; + + // We need to compare the current location with the previous + // or the recursive loop will never end because it will never try to lookup the agent again + if (!firstTime) + { + lookupAgent = true; + } + } + else + { + lookupAgent = true; + } + } + + m_log.DebugFormat("[XXX] Neeed lookup ? {0}", (lookupAgent ? "yes" : "no")); + + // Are we needing to look-up an agent? + if (lookupAgent) + { + bool isPresent = false; + // Non-cached user agent lookup. + PresenceInfo[] presences = m_PresenceService.GetAgents(new string[] { toAgentID.ToString() }); + if (presences != null && presences.Length > 0) + { + foreach (PresenceInfo p in presences) + { + if (p.RegionID != UUID.Zero) + { + upd = p; + break; + } + else + isPresent = true; + } + } + + if (upd == null && isPresent) + { + // Let's check with the UAS if the user is elsewhere + url = m_UserAgentService.LocateUser(toAgentID); + } + + if (upd != null || url != string.Empty) + { + // check if we've tried this before.. + // This is one way to end the recursive loop + // + if (!firstTime && ((previousLocation is PresenceInfo && upd != null && upd.RegionID == ((PresenceInfo)previousLocation).RegionID) || + (previousLocation is string && previousLocation.Equals(url)))) + { + // m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message"); + m_log.DebugFormat("[XXX] Fail 1 {0} {1}", previousLocation, url); + + return false; + } + } + } + + if (upd != null) + { + // ok, the user is around somewhere. Let's send back the reply with "success" + // even though the IM may still fail. Just don't keep the caller waiting for + // the entire time we're trying to deliver the IM + return SendIMToRegion(upd, im, toAgentID); + } + else if (url != string.Empty) + { + // ok, the user is around somewhere. Let's send back the reply with "success" + // even though the IM may still fail. Just don't keep the caller waiting for + // the entire time we're trying to deliver the IM + return ForwardIMToGrid(url, im, toAgentID); + } + else if (firstTime && previousLocation is string && (string)previousLocation != string.Empty) + { + return ForwardIMToGrid((string)previousLocation, im, toAgentID); + } + else + m_log.DebugFormat("[HG IM SERVICE]: Unable to locate user {0}", toAgentID); + return false; + } + + bool SendIMToRegion(PresenceInfo upd, GridInstantMessage im, UUID toAgentID) + { + bool imresult = false; + GridRegion reginfo = null; + if (!m_RegionCache.TryGetValue(upd.RegionID, out reginfo)) + { + reginfo = m_GridService.GetRegionByUUID(UUID.Zero /*!!!*/, upd.RegionID); + if (reginfo != null) + m_RegionCache.AddOrUpdate(upd.RegionID, reginfo, CACHE_EXPIRATION_SECONDS); + } + + if (reginfo != null) + imresult = InstantMessageServiceConnector.SendInstantMessage(reginfo.ServerURI, im); + else + return false; + + if (imresult) + { + // IM delivery successful, so store the Agent's location in our local cache. + lock (m_UserLocationMap) + { + if (m_UserLocationMap.ContainsKey(toAgentID)) + { + m_UserLocationMap[toAgentID] = upd; + } + else + { + m_UserLocationMap.Add(toAgentID, upd); + } + } + return true; + } + else + { + // try again, but lookup user this time. + // Warning, this must call the Async version + // of this method or we'll be making thousands of threads + // The version within the spawned thread is SendGridInstantMessageViaXMLRPCAsync + // The version that spawns the thread is SendGridInstantMessageViaXMLRPC + + // This is recursive!!!!! + return TrySendInstantMessage(im, upd, false); + } + } + + bool ForwardIMToGrid(string url, GridInstantMessage im, UUID toAgentID) + { + if (InstantMessageServiceConnector.SendInstantMessage(url, im)) + { + // IM delivery successful, so store the Agent's location in our local cache. + lock (m_UserLocationMap) + { + if (m_UserLocationMap.ContainsKey(toAgentID)) + { + m_UserLocationMap[toAgentID] = url; + } + else + { + m_UserLocationMap.Add(toAgentID, url); + } + } + + return true; + } + else + { + // try again, but lookup user this time. + // Warning, this must call the Async version + // of this method or we'll be making thousands of threads + // The version within the spawned thread is SendGridInstantMessageViaXMLRPCAsync + // The version that spawns the thread is SendGridInstantMessageViaXMLRPC + + // This is recursive!!!!! + return TrySendInstantMessage(im, url, false); + } + + } + } +} diff --git a/OpenSim/Services/HypergridService/UserAgentService.cs b/OpenSim/Services/HypergridService/UserAgentService.cs index e63f941cc3..59ad043cef 100644 --- a/OpenSim/Services/HypergridService/UserAgentService.cs +++ b/OpenSim/Services/HypergridService/UserAgentService.cs @@ -474,6 +474,17 @@ namespace OpenSim.Services.HypergridService return new Dictionary(); } + + public string LocateUser(UUID userID) + { + foreach (TravelingAgentInfo t in m_TravelingAgents.Values) + { + if (t.UserID == userID) + return t.GridExternalName; + } + + return string.Empty; + } } class TravelingAgentInfo diff --git a/OpenSim/Services/Interfaces/IGatekeeperService.cs b/OpenSim/Services/Interfaces/IGatekeeperService.cs index f1860cce58..ffab9eadcc 100644 --- a/OpenSim/Services/Interfaces/IGatekeeperService.cs +++ b/OpenSim/Services/Interfaces/IGatekeeperService.cs @@ -56,6 +56,8 @@ namespace OpenSim.Services.Interfaces GridRegion GetHomeRegion(UUID userID, out Vector3 position, out Vector3 lookAt); Dictionary GetServerURLs(UUID userID); + string LocateUser(UUID userID); + void StatusNotification(List friends, UUID userID, bool online); List GetOnlineFriends(UUID userID, List friends); @@ -63,4 +65,10 @@ namespace OpenSim.Services.Interfaces bool VerifyAgent(UUID sessionID, string token); bool VerifyClient(UUID sessionID, string reportedIP); } + + public interface IInstantMessage + { + bool IncomingInstantMessage(GridInstantMessage im); + bool OutgoingInstantMessage(GridInstantMessage im, string url); + } } diff --git a/OpenSim/Services/Interfaces/ISimulatorSocialService.cs b/OpenSim/Services/Interfaces/ISimulatorSocialService.cs index 0b7aefcf35..df9e7e7919 100644 --- a/OpenSim/Services/Interfaces/ISimulatorSocialService.cs +++ b/OpenSim/Services/Interfaces/ISimulatorSocialService.cs @@ -37,4 +37,9 @@ namespace OpenSim.Services.Interfaces { bool StatusNotify(UUID userID, UUID friendID, bool online); } + + public interface IInstantMessageSimConnector + { + bool SendInstantMessage(GridInstantMessage im); + } } diff --git a/bin/config-include/StandaloneHypergrid.ini b/bin/config-include/StandaloneHypergrid.ini index 6cd24b09fc..cd2d7a7bac 100644 --- a/bin/config-include/StandaloneHypergrid.ini +++ b/bin/config-include/StandaloneHypergrid.ini @@ -32,6 +32,9 @@ AuthenticationServiceInConnector = true SimulationServiceInConnector = true +[Messaging] + MessageTransferModule = HGMessageTransferModule + [SimulationDataStore] LocalServiceModule = "OpenSim.Services.Connectors.dll:SimulationDataService" @@ -148,6 +151,13 @@ LocalServiceModule = "OpenSim.Services.FriendsService.dll:FriendsService" UserAgentService = "OpenSim.Services.HypergridService.dll:UserAgentService" +[HGInstantMessageService] + LocalServiceModule = "OpenSim.Services.HypergridService.dll:HGInstantMessageService" + GridService = "OpenSim.Services.GridService.dll:GridService" + PresenceService = "OpenSim.Services.PresenceService.dll:PresenceService" + UserAgentService = "OpenSim.Services.HypergridService.dll:UserAgentService" + + ;; This should always be the very last thing on this file [Includes] Include-Common = "config-include/StandaloneCommon.ini"