682 lines
22 KiB
C#
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;
|
|
}
|
|
|
|
}
|
|
|
|
} |