OpenSimMirror/OpenSim/Region/Environment/Modules/XMLRPCModule.cs

682 lines
22 KiB
C#

/*
* 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 static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private Scene m_scene;
private object XMLRPCListLock = new object();
private string m_name = "XMLRPCModule";
private int RemoteReplyScriptWait = 300;
private int RemoteReplyScriptTimeout = 9000;
private int m_remoteDataPort = 0;
private List<Scene> m_scenes = new List<Scene>();
// <channel id, RPCChannelInfo>
private Dictionary<LLUUID, RPCChannelInfo> m_openChannels;
private Dictionary<LLUUID, RPCRequestInfo> m_rpcPending;
private Dictionary<LLUUID, RPCRequestInfo> m_rpcPendingResponses;
private Dictionary<LLUUID, SendRemoteDataRequest> m_pendingSRDResponses;
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_rpcPending = new Dictionary<LLUUID, RPCRequestInfo>();
m_rpcPendingResponses = new Dictionary<LLUUID, RPCRequestInfo>();
m_pendingSRDResponses = new Dictionary<LLUUID, SendRemoteDataRequest>();
// Start http server
// Attach xmlrpc handlers
m_log.Info("[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;
}
// Delete channels based on itemID
// for when a script is deleted
public void DeleteChannels(LLUUID itemID)
{
if (m_openChannels != null)
{
ArrayList tmp = new ArrayList();
lock (XMLRPCListLock)
{
foreach (RPCChannelInfo li in m_openChannels.Values)
{
if (li.GetItemID().Equals(itemID))
{
tmp.Add(itemID);
}
}
System.Collections.IEnumerator tmpEnumerator = tmp.GetEnumerator();
while ( tmpEnumerator.MoveNext() )
m_openChannels.Remove((LLUUID)tmpEnumerator.Current);
}
}
}
/**********************************************
* 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_rpcPendingResponses.TryGetValue(message_key, out rpcInfo))
{
rpcInfo.SetStrRetval(sdata);
rpcInfo.SetIntRetval(idata);
rpcInfo.SetProcessed(true);
m_rpcPendingResponses.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);
m_rpcPending.Add(rpcInfo.GetMessageID(), rpcInfo);
}
int timeoutCtr = 0;
while (!rpcInfo.IsProcessed() && (timeoutCtr < RemoteReplyScriptTimeout))
{
Thread.Sleep(RemoteReplyScriptWait);
timeoutCtr += RemoteReplyScriptWait;
}
if (rpcInfo.IsProcessed())
{
Hashtable param = new Hashtable();
param["StringValue"] = rpcInfo.GetStrRetval();
param["IntValue"] = Convert.ToString(rpcInfo.GetIntRetval());
ArrayList parameters = new ArrayList();
parameters.Add(param);
response.Value = parameters;
rpcInfo = null;
}
else
{
response.SetFault(-1, "Script timeout");
rpcInfo = null;
}
}
else
{
response.SetFault(-1, "Invalid channel");
}
}
return response;
}
public bool hasRequests()
{
lock (XMLRPCListLock)
{
if (m_rpcPending != null)
return (m_rpcPending.Count > 0);
else
return false;
}
}
public RPCRequestInfo GetNextCompletedRequest()
{
if (m_rpcPending != null)
{
lock (XMLRPCListLock)
{
foreach (LLUUID luid in m_rpcPending.Keys)
{
RPCRequestInfo tmpReq;
if (m_rpcPending.TryGetValue(luid, out tmpReq))
{
if (!tmpReq.IsProcessed()) return tmpReq;
}
}
}
}
return null;
}
public void RemoveCompletedRequest(LLUUID id)
{
lock (XMLRPCListLock)
{
RPCRequestInfo tmp;
if (m_rpcPending.TryGetValue(id, out tmp))
{
m_rpcPending.Remove(id);
m_rpcPendingResponses.Add(id, tmp);
}
else
{
Console.WriteLine("UNABLE TO REMOVE COMPLETED REQUEST");
}
}
}
public LLUUID SendRemoteData(uint localID, LLUUID itemID, string channel, string dest, int idata, string sdata)
{
SendRemoteDataRequest req = new SendRemoteDataRequest(
localID, itemID, channel, dest, idata, sdata
);
m_pendingSRDResponses.Add(req.GetReqID(), req);
return req.process();
}
public SendRemoteDataRequest GetNextCompletedSRDRequest()
{
if (m_pendingSRDResponses != null)
{
lock (XMLRPCListLock)
{
foreach (LLUUID luid in m_pendingSRDResponses.Keys)
{
SendRemoteDataRequest tmpReq;
if (m_pendingSRDResponses.TryGetValue(luid, out tmpReq))
{
if (tmpReq.finished)
return tmpReq;
}
}
}
}
return null;
}
public void RemoveCompletedSRDRequest(LLUUID id)
{
lock (XMLRPCListLock)
{
SendRemoteDataRequest tmpReq;
if (m_pendingSRDResponses.TryGetValue(id, out tmpReq))
{
m_pendingSRDResponses.Remove(id);
}
}
}
public void CancelSRDRequests(LLUUID itemID)
{
if (m_pendingSRDResponses != null)
{
lock (XMLRPCListLock)
{
foreach (SendRemoteDataRequest li in m_pendingSRDResponses.Values)
{
if (li.m_itemID.Equals(itemID))
m_pendingSRDResponses.Remove(li.GetReqID());
}
}
}
}
}
/**************************************************************
*
* 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_respStr;
private int m_respInt;
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_respStr = String.Empty;
m_respInt = 0;
}
public bool IsProcessed()
{
return m_processed;
}
public LLUUID GetChannelKey()
{
return m_ChannelKey;
}
public void SetProcessed(bool processed)
{
m_processed = processed;
}
public void SetStrRetval(string resp)
{
m_respStr = resp;
}
public string GetStrRetval()
{
return m_respStr;
}
public void SetIntRetval(int resp)
{
m_respInt = resp;
}
public int GetIntRetval()
{
return m_respInt;
}
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;
}
}
public class SendRemoteDataRequest
{
public LLUUID reqID;
public string destURL;
public string channel;
public string sdata;
public int idata;
public bool finished;
public string response_sdata;
public int response_idata;
public XmlRpcRequest request;
private Thread httpThread;
public LLUUID m_itemID;
public uint m_localID;
private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public SendRemoteDataRequest(uint localID, LLUUID itemID, string channel, string dest, int idata, string sdata)
{
this.channel = channel;
this.destURL = dest;
this.idata = idata;
this.sdata = sdata;
m_itemID = itemID;
m_localID = localID;
reqID = LLUUID.Random();
}
public LLUUID process()
{
httpThread = new Thread(SendRequest);
httpThread.Name = "HttpRequestThread";
httpThread.Priority = ThreadPriority.BelowNormal;
httpThread.IsBackground = true;
finished = false;
httpThread.Start();
OpenSim.Framework.ThreadTracker.Add(httpThread);
return reqID;
}
/*
* TODO: More work on the response codes. Right now
* returning 200 for success or 499 for exception
*/
public void SendRequest()
{
Hashtable param = new Hashtable();
// Check if channel is an LLUUID
// if not, use as method name
LLUUID parseUID;
string mName = "llRemoteData";
if( (channel != null) && (channel != "") )
if( !LLUUID.TryParse(channel, out parseUID) )
mName = channel;
else
param["Channel"] = channel;
param["StringValue"] = sdata;
param["IntValue"] = Convert.ToString(idata);
ArrayList parameters = new ArrayList();
parameters.Add(param);
XmlRpcRequest req = new XmlRpcRequest(mName, parameters);
try
{
XmlRpcResponse resp = req.Send(destURL, 30000);
if (resp != null)
{
Hashtable respParms;
if(resp.Value.GetType().Equals(Type.GetType("System.Collections.Hashtable"))) {
respParms = (Hashtable)resp.Value;
}
else {
ArrayList respData = (ArrayList)resp.Value;
respParms = (Hashtable)respData[0];
}
if (respParms != null)
{
if (respParms.Contains("StringValue"))
{
sdata = (string)respParms["StringValue"];
}
if (respParms.Contains("IntValue"))
{
idata = Convert.ToInt32((string)respParms["IntValue"]);
}
if (respParms.Contains("faultString"))
{
sdata = (string)respParms["faultString"];
}
if (respParms.Contains("faultCode"))
{
idata = Convert.ToInt32(respParms["faultCode"]);
}
}
}
}
catch (System.Net.WebException we)
{
sdata = we.Message;
m_log.Warn("[SendRemoteDataRequest]: Request failed");
m_log.Warn(we.StackTrace);
}
finished = true;
}
public void Stop()
{
try
{
httpThread.Abort();
}
catch (Exception)
{
}
}
public LLUUID GetReqID()
{
return reqID;
}
}
}