context: /*
   * 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 System.Threading;
  using log4net;
  using Nini.Config;
  using Nwc.XmlRpc;
  using OpenMetaverse;
  using OpenSim.Framework;
  using OpenSim.Framework.Servers;
  using OpenSim.Framework.Servers.HttpServer;
  using OpenSim.Region.Framework.Interfaces;
  using OpenSim.Region.Framework.Scenes;
  using Mono.Addins;
  
  /*****************************************************
   *
   * 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.CoreModules.Scripting.XMLRPC
  {
      [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "XMLRPCModule")]
      public class XMLRPCModule : ISharedRegionModule, IXMLRPC
      {
          private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  
          private string m_name = "XMLRPCModule";
  
          // <channel id, RPCChannelInfo>
          private Dictionary<UUID, RPCChannelInfo> m_openChannels;
          private Dictionary<UUID, SendRemoteDataRequest> m_pendingSRDResponses;
          private int m_remoteDataPort = 0;
          public int Port
          {
              get { return m_remoteDataPort; }
          }
  
          private Dictionary<UUID, RPCRequestInfo> m_rpcPending;
          private Dictionary<UUID, RPCRequestInfo> m_rpcPendingResponses;
          private List<Scene> m_scenes = new List<Scene>();
          private int RemoteReplyScriptTimeout = 9000;
          private int RemoteReplyScriptWait = 300;
          private object XMLRPCListLock = new object();
  
          #region ISharedRegionModule Members
  
          public void Initialise(IConfigSource config)
          {
              // We need to create these early because the scripts might be calling
              // But since this gets called for every region, we need to make sure they
              // get called only one time (or we lose any open channels)
              m_openChannels = new Dictionary<UUID, RPCChannelInfo>();
              m_rpcPending = new Dictionary<UUID, RPCRequestInfo>();
              m_rpcPendingResponses = new Dictionary<UUID, RPCRequestInfo>();
              m_pendingSRDResponses = new Dictionary<UUID, SendRemoteDataRequest>();
  
              try
              {
                  m_remoteDataPort = config.Configs["XMLRPC"].GetInt("XmlRpcPort", m_remoteDataPort);
              }
              catch (Exception)
              {
              }
          }
  
          public void PostInitialise()
          {
              if (IsEnabled())
              {
                  // Start http server
                  // Attach xmlrpc handlers
                  //                m_log.InfoFormat(
                  //                    "[XML RPC MODULE]: Starting up XMLRPC Server on port {0} for llRemoteData commands.",
                  //                    m_remoteDataPort);
  
                  IHttpServer httpServer = MainServer.GetHttpServer((uint)m_remoteDataPort);
                  httpServer.AddXmlRPCHandler("llRemoteData", XmlRpcRemoteData);
              }
          }
  
          public void AddRegion(Scene scene)
          {
              if (!IsEnabled())
                  return;
  
              if (!m_scenes.Contains(scene))
              {
                  m_scenes.Add(scene);
  
                  scene.RegisterModuleInterface<IXMLRPC>(this);
              }
          }
  
          public void RegionLoaded(Scene scene)
          {
          }
  
          public void RemoveRegion(Scene scene)
          {
              if (!IsEnabled())
                  return;
  
              if (m_scenes.Contains(scene))
              {
                  scene.UnregisterModuleInterface<IXMLRPC>(this);
                  m_scenes.Remove(scene);
              }
          }
  
          public void Close()
          {
          }
  
          public string Name
          {
              get { return m_name; }
          }
  
          public Type ReplaceableInterface
          {
              get { return null; }
          }
  
          #endregion
  
          #region IXMLRPC Members
  
          public bool IsEnabled()
          {
              return (m_remoteDataPort > 0);
          }
  
          /**********************************************
           * OpenXMLRPCChannel
           *
           * Generate a UUID channel key and add it and
           * the prim id to dictionary <channelUUID, primUUID>
           *
           * A custom channel key can be proposed.
           * Otherwise, passing UUID.Zero will generate
           * and return a random channel
           *
           * 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 UUID OpenXMLRPCChannel(uint localID, UUID itemID, UUID channelID)
          {
              UUID newChannel = UUID.Zero;
  
              // This should no longer happen, but the check is reasonable anyway
              if (null == m_openChannels)
              {
                  m_log.Warn("[XML RPC MODULE]: Attempt to open channel before initialization is complete");
                  return newChannel;
              }
  
              //Is a dupe?
              foreach (RPCChannelInfo ci in m_openChannels.Values)
              {
                  if (ci.GetItemID().Equals(itemID))
                  {
                      // return the original channel ID for this item
                      newChannel = ci.GetChannelID();
                      break;
                  }
              }
  
              if (newChannel == UUID.Zero)
              {
                  newChannel = (channelID == UUID.Zero) ? UUID.Random() : channelID;
                  RPCChannelInfo rpcChanInfo = new RPCChannelInfo(localID, itemID, newChannel);
                  lock (XMLRPCListLock)
                  {
                      m_openChannels.Add(newChannel, rpcChanInfo);
                  }
              }
  
              return newChannel;
          }
  
          // Delete channels based on itemID
          // for when a script is deleted
          public void DeleteChannels(UUID 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);
                          }
                      }
  
                      IEnumerator tmpEnumerator = tmp.GetEnumerator();
                      while (tmpEnumerator.MoveNext())
                          m_openChannels.Remove((UUID) tmpEnumerator.Current);
                  }
              }
          }
  
          /**********************************************
           * Remote Data Reply
           *
           * Response to RPC message
           *
           *********************************************/
  
          public void RemoteDataReply(string channel, string message_id, string sdata, int idata)
          {
              UUID message_key = new UUID(message_id);
              UUID channel_key = new UUID(channel);
  
              RPCRequestInfo rpcInfo = null;
  
              if (message_key == UUID.Zero)
              {
                  foreach (RPCRequestInfo oneRpcInfo in m_rpcPendingResponses.Values)
                      if (oneRpcInfo.GetChannelKey() == channel_key)
                          rpcInfo = oneRpcInfo;
              }
              else
              {
                  m_rpcPendingResponses.TryGetValue(message_key, out rpcInfo);
              }
  
              if (rpcInfo != null)
              {
                  rpcInfo.SetStrRetval(sdata);
                  rpcInfo.SetIntRetval(idata);
                  rpcInfo.SetProcessed(true);
                  m_rpcPendingResponses.Remove(message_key);
              }
              else
              {
                  m_log.Warn("[XML RPC MODULE]: Channel or message_id not found");
              }
          }
  
          /**********************************************
           * CloseXMLRPCChannel
           *
           * Remove channel from dictionary
           *
           *********************************************/
  
          public void CloseXMLRPCChannel(UUID channelKey)
          {
              if (m_openChannels.ContainsKey(channelKey))
                  m_openChannels.Remove(channelKey);
          }
  
  
          public bool hasRequests()
          {
              lock (XMLRPCListLock)
              {
                  if (m_rpcPending != null)
                      return (m_rpcPending.Count > 0);
                  else
                      return false;
              }
          }
  
          public IXmlRpcRequestInfo GetNextCompletedRequest()
          {
              if (m_rpcPending != null)
              {
                  lock (XMLRPCListLock)
                  {
                      foreach (UUID luid in m_rpcPending.Keys)
                      {
                          RPCRequestInfo tmpReq;
  
                          if (m_rpcPending.TryGetValue(luid, out tmpReq))
                          {
                              if (!tmpReq.IsProcessed()) return tmpReq;
                          }
                      }
                  }
              }
              return null;
          }
  
          public void RemoveCompletedRequest(UUID id)
          {
              lock (XMLRPCListLock)
              {
                  RPCRequestInfo tmp;
                  if (m_rpcPending.TryGetValue(id, out tmp))
                  {
                      m_rpcPending.Remove(id);
                      m_rpcPendingResponses.Add(id, tmp);
                  }
                  else
                  {
                      m_log.Error("[XML RPC MODULE]: UNABLE TO REMOVE COMPLETED REQUEST");
                  }
              }
          }
  
          public UUID SendRemoteData(uint localID, UUID 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);
              req.Process();
              return req.ReqID;
          }
  
          public IServiceRequest GetNextCompletedSRDRequest()
          {
              if (m_pendingSRDResponses != null)
              {
                  lock (XMLRPCListLock)
                  {
                      foreach (UUID luid in m_pendingSRDResponses.Keys)
                      {
                          SendRemoteDataRequest tmpReq;
  
                          if (m_pendingSRDResponses.TryGetValue(luid, out tmpReq))
                          {
                              if (tmpReq.Finished)
                                  return tmpReq;
                          }
                      }
                  }
              }
              return null;
          }
  
          public void RemoveCompletedSRDRequest(UUID id)
          {
              lock (XMLRPCListLock)
              {
                  SendRemoteDataRequest tmpReq;
                  if (m_pendingSRDResponses.TryGetValue(id, out tmpReq))
                  {
                      m_pendingSRDResponses.Remove(id);
                  }
              }
          }
  
          public void CancelSRDRequests(UUID itemID)
          {
              if (m_pendingSRDResponses != null)
              {
                  lock (XMLRPCListLock)
                  {
                      foreach (SendRemoteDataRequest li in m_pendingSRDResponses.Values)
                      {
                          if (li.ItemID.Equals(itemID))
                              m_pendingSRDResponses.Remove(li.GetReqID());
                      }
                  }
              }
          }
  
          #endregion
  
          public XmlRpcResponse XmlRpcRemoteData(XmlRpcRequest request, IPEndPoint remoteClient)
          {
              XmlRpcResponse response = new XmlRpcResponse();
  
              Hashtable requestData = (Hashtable) request.Params[0];
              bool GoodXML = (requestData.Contains("Channel") && requestData.Contains("IntValue") &&
                              requestData.Contains("StringValue"));
  
              if (GoodXML)
              {
  
gt:                 UUID channel = new UUID((string) requestData["Channel"]);