OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
XMLRPCModule.cs
Go to the documentation of this file.
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;
30 using System.Collections.Generic;
31 using System.Net;
32 using System.Reflection;
33 using System.Threading;
34 using log4net;
35 using Nini.Config;
36 using Nwc.XmlRpc;
37 using OpenMetaverse;
38 using OpenSim.Framework;
39 using OpenSim.Framework.Monitoring;
40 using OpenSim.Framework.Servers;
41 using OpenSim.Framework.Servers.HttpServer;
42 using OpenSim.Region.Framework.Interfaces;
43 using OpenSim.Region.Framework.Scenes;
44 using Mono.Addins;
45 
46 /*****************************************************
47  *
48  * XMLRPCModule
49  *
50  * Module for accepting incoming communications from
51  * external XMLRPC client and calling a remote data
52  * procedure for a registered data channel/prim.
53  *
54  *
55  * 1. On module load, open a listener port
56  * 2. Attach an XMLRPC handler
57  * 3. When a request is received:
58  * 3.1 Parse into components: channel key, int, string
59  * 3.2 Look up registered channel listeners
60  * 3.3 Call the channel (prim) remote data method
61  * 3.4 Capture the response (llRemoteDataReply)
62  * 3.5 Return response to client caller
63  * 3.6 If no response from llRemoteDataReply within
64  * RemoteReplyScriptTimeout, generate script timeout fault
65  *
66  * Prims in script must:
67  * 1. Open a remote data channel
68  * 1.1 Generate a channel ID
69  * 1.2 Register primid,channelid pair with module
70  * 2. Implement the remote data procedure handler
71  *
72  * llOpenRemoteDataChannel
73  * llRemoteDataReply
74  * remote_data(integer type, key channel, key messageid, string sender, integer ival, string sval)
75  * llCloseRemoteDataChannel
76  *
77  * **************************************************/
78 
79 namespace OpenSim.Region.CoreModules.Scripting.XMLRPC
80 {
81  [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "XMLRPCModule")]
83  {
84  private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
85 
86  private string m_name = "XMLRPCModule";
87 
88  // <channel id, RPCChannelInfo>
89  private Dictionary<UUID, RPCChannelInfo> m_openChannels;
90  private Dictionary<UUID, SendRemoteDataRequest> m_pendingSRDResponses;
91  private int m_remoteDataPort = 0;
92  public int Port
93  {
94  get { return m_remoteDataPort; }
95  }
96 
97  private Dictionary<UUID, RPCRequestInfo> m_rpcPending;
98  private Dictionary<UUID, RPCRequestInfo> m_rpcPendingResponses;
99  private List<Scene> m_scenes = new List<Scene>();
100  private int RemoteReplyScriptTimeout = 9000;
101  private int RemoteReplyScriptWait = 300;
102  private object XMLRPCListLock = new object();
103 
104  #region ISharedRegionModule Members
105 
106  public void Initialise(IConfigSource config)
107  {
108  // We need to create these early because the scripts might be calling
109  // But since this gets called for every region, we need to make sure they
110  // get called only one time (or we lose any open channels)
111  m_openChannels = new Dictionary<UUID, RPCChannelInfo>();
112  m_rpcPending = new Dictionary<UUID, RPCRequestInfo>();
113  m_rpcPendingResponses = new Dictionary<UUID, RPCRequestInfo>();
114  m_pendingSRDResponses = new Dictionary<UUID, SendRemoteDataRequest>();
115  if (config.Configs["XMLRPC"] != null)
116  {
117  try
118  {
119  m_remoteDataPort = config.Configs["XMLRPC"].GetInt("XmlRpcPort", m_remoteDataPort);
120  }
121  catch (Exception)
122  {
123  }
124  }
125  }
126 
127  public void PostInitialise()
128  {
129  if (IsEnabled())
130  {
131  // Start http server
132  // Attach xmlrpc handlers
133  // m_log.InfoFormat(
134  // "[XML RPC MODULE]: Starting up XMLRPC Server on port {0} for llRemoteData commands.",
135  // m_remoteDataPort);
136 
137  IHttpServer httpServer = MainServer.GetHttpServer((uint)m_remoteDataPort);
138  httpServer.AddXmlRPCHandler("llRemoteData", XmlRpcRemoteData);
139  }
140  }
141 
142  public void AddRegion(Scene scene)
143  {
144  if (!IsEnabled())
145  return;
146 
147  if (!m_scenes.Contains(scene))
148  {
149  m_scenes.Add(scene);
150 
151  scene.RegisterModuleInterface<IXMLRPC>(this);
152  }
153  }
154 
155  public void RegionLoaded(Scene scene)
156  {
157  }
158 
159  public void RemoveRegion(Scene scene)
160  {
161  if (!IsEnabled())
162  return;
163 
164  if (m_scenes.Contains(scene))
165  {
166  scene.UnregisterModuleInterface<IXMLRPC>(this);
167  m_scenes.Remove(scene);
168  }
169  }
170 
171  public void Close()
172  {
173  }
174 
175  public string Name
176  {
177  get { return m_name; }
178  }
179 
180  public Type ReplaceableInterface
181  {
182  get { return null; }
183  }
184 
185  #endregion
186 
187  #region IXMLRPC Members
188 
189  public bool IsEnabled()
190  {
191  return (m_remoteDataPort > 0);
192  }
193 
194  /**********************************************
195  * OpenXMLRPCChannel
196  *
197  * Generate a UUID channel key and add it and
198  * the prim id to dictionary <channelUUID, primUUID>
199  *
200  * A custom channel key can be proposed.
201  * Otherwise, passing UUID.Zero will generate
202  * and return a random channel
203  *
204  * First check if there is a channel assigned for
205  * this itemID. If there is, then someone called
206  * llOpenRemoteDataChannel twice. Just return the
207  * original channel. Other option is to delete the
208  * current channel and assign a new one.
209  *
210  * ********************************************/
211 
212  public UUID OpenXMLRPCChannel(uint localID, UUID itemID, UUID channelID)
213  {
214  UUID newChannel = UUID.Zero;
215 
216  // This should no longer happen, but the check is reasonable anyway
217  if (null == m_openChannels)
218  {
219  m_log.Warn("[XML RPC MODULE]: Attempt to open channel before initialization is complete");
220  return newChannel;
221  }
222 
223  //Is a dupe?
224  foreach (RPCChannelInfo ci in m_openChannels.Values)
225  {
226  if (ci.GetItemID().Equals(itemID))
227  {
228  // return the original channel ID for this item
229  newChannel = ci.GetChannelID();
230  break;
231  }
232  }
233 
234  if (newChannel == UUID.Zero)
235  {
236  newChannel = (channelID == UUID.Zero) ? UUID.Random() : channelID;
237  RPCChannelInfo rpcChanInfo = new RPCChannelInfo(localID, itemID, newChannel);
238  lock (XMLRPCListLock)
239  {
240  m_openChannels.Add(newChannel, rpcChanInfo);
241  }
242  }
243 
244  return newChannel;
245  }
246 
247  // Delete channels based on itemID
248  // for when a script is deleted
249  public void DeleteChannels(UUID itemID)
250  {
251  if (m_openChannels != null)
252  {
253  ArrayList tmp = new ArrayList();
254 
255  lock (XMLRPCListLock)
256  {
257  foreach (RPCChannelInfo li in m_openChannels.Values)
258  {
259  if (li.GetItemID().Equals(itemID))
260  {
261  tmp.Add(itemID);
262  }
263  }
264 
265  IEnumerator tmpEnumerator = tmp.GetEnumerator();
266  while (tmpEnumerator.MoveNext())
267  m_openChannels.Remove((UUID) tmpEnumerator.Current);
268  }
269  }
270  }
271 
272  /**********************************************
273  * Remote Data Reply
274  *
275  * Response to RPC message
276  *
277  *********************************************/
278 
279  public void RemoteDataReply(string channel, string message_id, string sdata, int idata)
280  {
281  UUID message_key = new UUID(message_id);
282  UUID channel_key = new UUID(channel);
283 
284  RPCRequestInfo rpcInfo = null;
285 
286  if (message_key == UUID.Zero)
287  {
288  foreach (RPCRequestInfo oneRpcInfo in m_rpcPendingResponses.Values)
289  if (oneRpcInfo.GetChannelKey() == channel_key)
290  rpcInfo = oneRpcInfo;
291  }
292  else
293  {
294  m_rpcPendingResponses.TryGetValue(message_key, out rpcInfo);
295  }
296 
297  if (rpcInfo != null)
298  {
299  rpcInfo.SetStrRetval(sdata);
300  rpcInfo.SetIntRetval(idata);
301  rpcInfo.SetProcessed(true);
302  m_rpcPendingResponses.Remove(message_key);
303  }
304  else
305  {
306  m_log.Warn("[XML RPC MODULE]: Channel or message_id not found");
307  }
308  }
309 
310  /**********************************************
311  * CloseXMLRPCChannel
312  *
313  * Remove channel from dictionary
314  *
315  *********************************************/
316 
317  public void CloseXMLRPCChannel(UUID channelKey)
318  {
319  if (m_openChannels.ContainsKey(channelKey))
320  m_openChannels.Remove(channelKey);
321  }
322 
323 
324  public bool hasRequests()
325  {
326  lock (XMLRPCListLock)
327  {
328  if (m_rpcPending != null)
329  return (m_rpcPending.Count > 0);
330  else
331  return false;
332  }
333  }
334 
336  {
337  if (m_rpcPending != null)
338  {
339  lock (XMLRPCListLock)
340  {
341  foreach (UUID luid in m_rpcPending.Keys)
342  {
343  RPCRequestInfo tmpReq;
344 
345  if (m_rpcPending.TryGetValue(luid, out tmpReq))
346  {
347  if (!tmpReq.IsProcessed()) return tmpReq;
348  }
349  }
350  }
351  }
352  return null;
353  }
354 
355  public void RemoveCompletedRequest(UUID id)
356  {
357  lock (XMLRPCListLock)
358  {
359  RPCRequestInfo tmp;
360  if (m_rpcPending.TryGetValue(id, out tmp))
361  {
362  m_rpcPending.Remove(id);
363  m_rpcPendingResponses.Add(id, tmp);
364  }
365  else
366  {
367  m_log.Error("[XML RPC MODULE]: UNABLE TO REMOVE COMPLETED REQUEST");
368  }
369  }
370  }
371 
372  public UUID SendRemoteData(uint localID, UUID itemID, string channel, string dest, int idata, string sdata)
373  {
375  localID, itemID, channel, dest, idata, sdata
376  );
377  m_pendingSRDResponses.Add(req.GetReqID(), req);
378  req.Process();
379  return req.ReqID;
380  }
381 
383  {
384  if (m_pendingSRDResponses != null)
385  {
386  lock (XMLRPCListLock)
387  {
388  foreach (UUID luid in m_pendingSRDResponses.Keys)
389  {
390  SendRemoteDataRequest tmpReq;
391 
392  if (m_pendingSRDResponses.TryGetValue(luid, out tmpReq))
393  {
394  if (tmpReq.Finished)
395  return tmpReq;
396  }
397  }
398  }
399  }
400  return null;
401  }
402 
403  public void RemoveCompletedSRDRequest(UUID id)
404  {
405  lock (XMLRPCListLock)
406  {
407  SendRemoteDataRequest tmpReq;
408  if (m_pendingSRDResponses.TryGetValue(id, out tmpReq))
409  {
410  m_pendingSRDResponses.Remove(id);
411  }
412  }
413  }
414 
415  public void CancelSRDRequests(UUID itemID)
416  {
417  if (m_pendingSRDResponses != null)
418  {
419  lock (XMLRPCListLock)
420  {
421  foreach (SendRemoteDataRequest li in m_pendingSRDResponses.Values)
422  {
423  if (li.ItemID.Equals(itemID))
424  m_pendingSRDResponses.Remove(li.GetReqID());
425  }
426  }
427  }
428  }
429 
430  #endregion
431 
432  public XmlRpcResponse XmlRpcRemoteData(XmlRpcRequest request, IPEndPoint remoteClient)
433  {
434  XmlRpcResponse response = new XmlRpcResponse();
435 
436  Hashtable requestData = (Hashtable) request.Params[0];
437  bool GoodXML = (requestData.Contains("Channel") && requestData.Contains("IntValue") &&
438  requestData.Contains("StringValue"));
439 
440  if (GoodXML)
441  {
442  UUID channel = new UUID((string) requestData["Channel"]);
443  RPCChannelInfo rpcChanInfo;
444  if (m_openChannels.TryGetValue(channel, out rpcChanInfo))
445  {
446  string intVal = Convert.ToInt32(requestData["IntValue"]).ToString();
447  string strVal = (string) requestData["StringValue"];
448 
449  RPCRequestInfo rpcInfo;
450 
451  lock (XMLRPCListLock)
452  {
453  rpcInfo =
454  new RPCRequestInfo(rpcChanInfo.GetLocalID(), rpcChanInfo.GetItemID(), channel, strVal,
455  intVal);
456  m_rpcPending.Add(rpcInfo.GetMessageID(), rpcInfo);
457  }
458 
459  int timeoutCtr = 0;
460 
461  while (!rpcInfo.IsProcessed() && (timeoutCtr < RemoteReplyScriptTimeout))
462  {
463  Thread.Sleep(RemoteReplyScriptWait);
464  timeoutCtr += RemoteReplyScriptWait;
465  }
466  if (rpcInfo.IsProcessed())
467  {
468  Hashtable param = new Hashtable();
469  param["StringValue"] = rpcInfo.GetStrRetval();
470  param["IntValue"] = rpcInfo.GetIntRetval();
471 
472  ArrayList parameters = new ArrayList();
473  parameters.Add(param);
474 
475  response.Value = parameters;
476  rpcInfo = null;
477  }
478  else
479  {
480  response.SetFault(-1, "Script timeout");
481  rpcInfo = null;
482  }
483  }
484  else
485  {
486  response.SetFault(-1, "Invalid channel");
487  }
488  }
489 
490  return response;
491  }
492  }
493 
495  {
496  private UUID m_ChannelKey;
497  private string m_IntVal;
498  private UUID m_ItemID;
499  private uint m_localID;
500  private UUID m_MessageID;
501  private bool m_processed;
502  private int m_respInt;
503  private string m_respStr;
504  private string m_StrVal;
505 
506  public RPCRequestInfo(uint localID, UUID itemID, UUID channelKey, string strVal, string intVal)
507  {
508  m_localID = localID;
509  m_StrVal = strVal;
510  m_IntVal = intVal;
511  m_ItemID = itemID;
512  m_ChannelKey = channelKey;
513  m_MessageID = UUID.Random();
514  m_processed = false;
515  m_respStr = String.Empty;
516  m_respInt = 0;
517  }
518 
519  public bool IsProcessed()
520  {
521  return m_processed;
522  }
523 
525  {
526  return m_ChannelKey;
527  }
528 
529  public void SetProcessed(bool processed)
530  {
531  m_processed = processed;
532  }
533 
534  public void SetStrRetval(string resp)
535  {
536  m_respStr = resp;
537  }
538 
539  public string GetStrRetval()
540  {
541  return m_respStr;
542  }
543 
544  public void SetIntRetval(int resp)
545  {
546  m_respInt = resp;
547  }
548 
549  public int GetIntRetval()
550  {
551  return m_respInt;
552  }
553 
554  public uint GetLocalID()
555  {
556  return m_localID;
557  }
558 
559  public UUID GetItemID()
560  {
561  return m_ItemID;
562  }
563 
564  public string GetStrVal()
565  {
566  return m_StrVal;
567  }
568 
569  public int GetIntValue()
570  {
571  return int.Parse(m_IntVal);
572  }
573 
575  {
576  return m_MessageID;
577  }
578  }
579 
580  public class RPCChannelInfo
581  {
582  private UUID m_ChannelKey;
583  private UUID m_itemID;
584  private uint m_localID;
585 
586  public RPCChannelInfo(uint localID, UUID itemID, UUID channelID)
587  {
588  m_ChannelKey = channelID;
589  m_localID = localID;
590  m_itemID = itemID;
591  }
592 
593  public UUID GetItemID()
594  {
595  return m_itemID;
596  }
597 
599  {
600  return m_ChannelKey;
601  }
602 
603  public uint GetLocalID()
604  {
605  return m_localID;
606  }
607  }
608 
610  {
611  private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
612 
613  public string Channel;
614  public string DestURL;
615  private bool _finished;
616  public bool Finished
617  {
618  get { return _finished; }
619  set { _finished = value; }
620  }
621  private Thread httpThread;
622  public int Idata;
623  private UUID _itemID;
624  public UUID ItemID
625  {
626  get { return _itemID; }
627  set { _itemID = value; }
628  }
629  private uint _localID;
630  public uint LocalID
631  {
632  get { return _localID; }
633  set { _localID = value; }
634  }
635  private UUID _reqID;
636  public UUID ReqID
637  {
638  get { return _reqID; }
639  set { _reqID = value; }
640  }
641  public XmlRpcRequest Request;
642  public int ResponseIdata;
643  public string ResponseSdata;
644  public string Sdata;
645 
646  public SendRemoteDataRequest(uint localID, UUID itemID, string channel, string dest, int idata, string sdata)
647  {
648  this.Channel = channel;
649  DestURL = dest;
650  this.Idata = idata;
651  this.Sdata = sdata;
652  ItemID = itemID;
653  LocalID = localID;
654 
655  ReqID = UUID.Random();
656  }
657 
658  public void Process()
659  {
660  _finished = false;
661  httpThread = WorkManager.StartThread(SendRequest, "HttpRequestThread", ThreadPriority.BelowNormal, true, false);
662  }
663 
664  /*
665  * TODO: More work on the response codes. Right now
666  * returning 200 for success or 499 for exception
667  */
668 
669  public void SendRequest()
670  {
671  Hashtable param = new Hashtable();
672 
673  // Check if channel is an UUID
674  // if not, use as method name
675  UUID parseUID;
676  string mName = "llRemoteData";
677  if (!string.IsNullOrEmpty(Channel))
678  if (!UUID.TryParse(Channel, out parseUID))
679  mName = Channel;
680  else
681  param["Channel"] = Channel;
682 
683  param["StringValue"] = Sdata;
684  param["IntValue"] = Convert.ToString(Idata);
685 
686  ArrayList parameters = new ArrayList();
687  parameters.Add(param);
688  XmlRpcRequest req = new XmlRpcRequest(mName, parameters);
689  try
690  {
691  XmlRpcResponse resp = req.Send(DestURL, 30000);
692  if (resp != null)
693  {
694  Hashtable respParms;
695  if (resp.Value.GetType().Equals(typeof(Hashtable)))
696  {
697  respParms = (Hashtable) resp.Value;
698  }
699  else
700  {
701  ArrayList respData = (ArrayList) resp.Value;
702  respParms = (Hashtable) respData[0];
703  }
704  if (respParms != null)
705  {
706  if (respParms.Contains("StringValue"))
707  {
708  Sdata = (string) respParms["StringValue"];
709  }
710  if (respParms.Contains("IntValue"))
711  {
712  Idata = Convert.ToInt32(respParms["IntValue"]);
713  }
714  if (respParms.Contains("faultString"))
715  {
716  Sdata = (string) respParms["faultString"];
717  }
718  if (respParms.Contains("faultCode"))
719  {
720  Idata = Convert.ToInt32(respParms["faultCode"]);
721  }
722  }
723  }
724  }
725  catch (Exception we)
726  {
727  Sdata = we.Message;
728  m_log.Warn("[SendRemoteDataRequest]: Request failed");
729  m_log.Warn(we.StackTrace);
730  }
731 
732  _finished = true;
733 
734  Watchdog.RemoveThread();
735  }
736 
737  public void Stop()
738  {
739  try
740  {
741  if (httpThread != null)
742  {
743  Watchdog.AbortThread(httpThread.ManagedThreadId);
744  httpThread = null;
745  }
746  }
747  catch (Exception)
748  {
749  }
750  }
751 
752  public UUID GetReqID()
753  {
754  return ReqID;
755  }
756  }
757 }
UUID OpenXMLRPCChannel(uint localID, UUID itemID, UUID channelID)
void RemoteDataReply(string channel, string message_id, string sdata, int idata)
void RegionLoaded(Scene scene)
This will be called once for every scene loaded. In a shared module this will be multiple times in on...
RPCChannelInfo(uint localID, UUID itemID, UUID channelID)
Interface to OpenSimulator's built in HTTP server. Use this to register handlers (http, llsd, xmlrpc, etc.) for given URLs.
Definition: IHttpServer.cs:36
void PostInitialise()
This is called exactly once after all the shared region-modules have been instanciated and IRegionMod...
void AddRegion(Scene scene)
This is called whenever a Scene is added. For shared modules, this can happen several times...
void Close()
This is the inverse to Initialise. After a Close(), this instance won't be usable anymore...
void RemoveRegion(Scene scene)
This is called whenever a Scene is removed. For shared modules, this can happen several times...
UUID SendRemoteData(uint localID, UUID itemID, string channel, string dest, int idata, string sdata)
XmlRpcResponse XmlRpcRemoteData(XmlRpcRequest request, IPEndPoint remoteClient)
Interactive OpenSim region server
Definition: OpenSim.cs:55
void Initialise(IConfigSource config)
This is called to initialize the region module. For shared modules, this is called exactly once...
RPCRequestInfo(uint localID, UUID itemID, UUID channelKey, string strVal, string intVal)
SendRemoteDataRequest(uint localID, UUID itemID, string channel, string dest, int idata, string sdata)