/* * 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 OpenSim 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.Threading; using libsecondlife; using Nini.Config; using Nwc.XmlRpc; using OpenSim.Framework.Console; using OpenSim.Framework.Servers; using OpenSim.Region.Environment.Interfaces; using OpenSim.Region.Environment.Scenes; /***************************************************** * * XMLRPCModule * * Module for accepting incoming communications from * external XMLRPC client and calling a remote data * procedure for a registered data channel/prim. * * * 1. On module load, open a listener port * 2. Attach an XMLRPC handler * 3. When a request is received: * 3.1 Parse into components: channel key, int, string * 3.2 Look up registered channel listeners * 3.3 Call the channel (prim) remote data method * 3.4 Capture the response (llRemoteDataReply) * 3.5 Return response to client caller * 3.6 If no response from llRemoteDataReply within * RemoteReplyScriptTimeout, generate script timeout fault * * Prims in script must: * 1. Open a remote data channel * 1.1 Generate a channel ID * 1.2 Register primid,channelid pair with module * 2. Implement the remote data procedure handler * * llOpenRemoteDataChannel * llRemoteDataReply * remote_data(integer type, key channel, key messageid, string sender, integer ival, string sval) * llCloseRemoteDataChannel * * **************************************************/ namespace OpenSim.Region.Environment.Modules { public class XMLRPCModule : IRegionModule, IXMLRPC { private Scene m_scene; private Queue<RPCRequestInfo> rpcQueue = new Queue<RPCRequestInfo>(); private object XMLRPCListLock = new object(); private string m_name = "XMLRPCModule"; private int RemoteReplyScriptWait = 300; private int RemoteReplyScriptTimeout = 900; private int m_remoteDataPort = 0; private List<Scene> m_scenes = new List<Scene>(); private LogBase m_log; // <channel id, RPCChannelInfo> private Dictionary<LLUUID, RPCChannelInfo> m_openChannels; // <channel id, RPCRequestInfo> private Dictionary<LLUUID, RPCRequestInfo> m_pendingResponse; public XMLRPCModule() { m_log = MainLog.Instance; } public void Initialise(Scene scene, IConfigSource config) { try { m_remoteDataPort = config.Configs["Network"].GetInt("remoteDataPort", m_remoteDataPort); } catch (Exception) { } if (!m_scenes.Contains(scene)) { m_scenes.Add(scene); scene.RegisterModuleInterface<IXMLRPC>(this); } } public void PostInitialise() { if (IsEnabled()) { m_openChannels = new Dictionary<LLUUID, RPCChannelInfo>(); m_pendingResponse = new Dictionary<LLUUID, RPCRequestInfo>(); // Start http server // Attach xmlrpc handlers m_log.Verbose("REMOTE_DATA", "Starting XMLRPC Server on port " + m_remoteDataPort + " for llRemoteData commands."); BaseHttpServer httpServer = new BaseHttpServer((uint) m_remoteDataPort); httpServer.AddXmlRPCHandler("llRemoteData", XmlRpcRemoteData); httpServer.Start(); } } public void Close() { } public string Name { get { return m_name; } } public bool IsSharedModule { get { return true; } } public bool IsEnabled() { return (m_remoteDataPort > 0); } /********************************************** * OpenXMLRPCChannel * * Generate a LLUUID channel key and add it and * the prim id to dictionary <channelUUID, primUUID> * * First check if there is a channel assigned for * this itemID. If there is, then someone called * llOpenRemoteDataChannel twice. Just return the * original channel. Other option is to delete the * current channel and assign a new one. * * ********************************************/ public LLUUID OpenXMLRPCChannel(uint localID, LLUUID itemID) { LLUUID channel = null; //Is a dupe? foreach (RPCChannelInfo ci in m_openChannels.Values) { if (ci.GetItemID().Equals(itemID)) { // return the original channel ID for this item channel = ci.GetChannelID(); break; } } if ((channel.Equals(null)) || (channel.Equals(LLUUID.Zero))) { channel = LLUUID.Random(); RPCChannelInfo rpcChanInfo = new RPCChannelInfo(localID, itemID, channel); lock (XMLRPCListLock) { m_openChannels.Add(channel, rpcChanInfo); } } return channel; } /********************************************** * Remote Data Reply * * Response to RPC message * *********************************************/ public void RemoteDataReply(string channel, string message_id, string sdata, int idata) { RPCRequestInfo rpcInfo; LLUUID message_key = new LLUUID(message_id); if (m_pendingResponse.TryGetValue(message_key, out rpcInfo)) { rpcInfo.SetRetval(sdata); rpcInfo.SetProcessed(true); lock (XMLRPCListLock) { m_pendingResponse.Remove(message_key); } } } /********************************************** * CloseXMLRPCChannel * * Remove channel from dictionary * *********************************************/ public void CloseXMLRPCChannel(LLUUID channelKey) { if (m_openChannels.ContainsKey(channelKey)) m_openChannels.Remove(channelKey); } public XmlRpcResponse XmlRpcRemoteData(XmlRpcRequest request) { XmlRpcResponse response = new XmlRpcResponse(); Hashtable requestData = (Hashtable) request.Params[0]; bool GoodXML = (requestData.Contains("Channel") && requestData.Contains("IntValue") && requestData.Contains("StringValue")); if (GoodXML) { LLUUID channel = new LLUUID((string) requestData["Channel"]); RPCChannelInfo rpcChanInfo; if (m_openChannels.TryGetValue(channel, out rpcChanInfo)) { string intVal = (string) requestData["IntValue"]; string strVal = (string) requestData["StringValue"]; RPCRequestInfo rpcInfo; lock (XMLRPCListLock) { rpcInfo = new RPCRequestInfo(rpcChanInfo.GetLocalID(), rpcChanInfo.GetItemID(), channel, strVal, intVal); rpcQueue.Enqueue(rpcInfo); } int timeoutCtr = 0; while (!rpcInfo.IsProcessed() && (timeoutCtr < RemoteReplyScriptTimeout)) { Thread.Sleep(RemoteReplyScriptWait); timeoutCtr += RemoteReplyScriptWait; } if (rpcInfo.IsProcessed()) { response.Value = rpcInfo.GetRetval(); rpcInfo = null; } else { response.SetFault(-1, "Script timeout"); lock (XMLRPCListLock) { m_pendingResponse.Remove(rpcInfo.GetMessageID()); } } } else { response.SetFault(-1, "Invalid channel"); } } return response; } public bool hasRequests() { return (rpcQueue.Count > 0); } public RPCRequestInfo GetNextRequest() { lock (XMLRPCListLock) { RPCRequestInfo rpcInfo = rpcQueue.Dequeue(); m_pendingResponse.Add(rpcInfo.GetMessageID(), rpcInfo); return rpcInfo; } } } /************************************************************** * * Class RPCRequestInfo * * Holds details about incoming requests until they are picked * from the queue by LSLLongCmdHandler * ***********************************************************/ public class RPCRequestInfo { private string m_StrVal; private string m_IntVal; private bool m_processed; private string m_resp; private uint m_localID; private LLUUID m_ItemID; private LLUUID m_MessageID; private LLUUID m_ChannelKey; public RPCRequestInfo(uint localID, LLUUID itemID, LLUUID channelKey, string strVal, string intVal) { m_localID = localID; m_StrVal = strVal; m_IntVal = intVal; m_ItemID = itemID; m_ChannelKey = channelKey; m_MessageID = LLUUID.Random(); m_processed = false; m_resp = ""; } public bool IsProcessed() { return m_processed; } public LLUUID GetChannelKey() { return m_ChannelKey; } public void SetProcessed(bool processed) { m_processed = processed; } public void SetRetval(string resp) { m_resp = resp; } public string GetRetval() { return m_resp; } public uint GetLocalID() { return m_localID; } public LLUUID GetItemID() { return m_ItemID; } public string GetStrVal() { return m_StrVal; } public int GetIntValue() { return int.Parse(m_IntVal); } public LLUUID GetMessageID() { return m_MessageID; } } public class RPCChannelInfo { private LLUUID m_itemID; private uint m_localID; private LLUUID m_ChannelKey; public RPCChannelInfo(uint localID, LLUUID itemID, LLUUID channelID) { m_ChannelKey = channelID; m_localID = localID; m_itemID = itemID; } public LLUUID GetItemID() { return m_itemID; } public LLUUID GetChannelID() { return m_ChannelKey; } public uint GetLocalID() { return m_localID; } } }