/Aurora/Modules/Scripting/XMLRPC/XMLRPCModule.cs
C# | 793 lines | 584 code | 99 blank | 110 comment | 62 complexity | 70af6071e193f2996ad262000e2ecbd8 MD5 | raw file
1/* 2 * Copyright (c) Contributors, http://aurora-sim.org/, 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 Aurora-Sim 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 28using Aurora.Framework; 29using Aurora.Framework.ConsoleFramework; 30using Aurora.Framework.Modules; 31using Aurora.Framework.SceneInfo; 32using Aurora.Framework.Servers; 33using Aurora.Framework.Servers.HttpServer; 34using Aurora.Framework.Servers.HttpServer.Interfaces; 35using Aurora.Framework.Utilities; 36using Nini.Config; 37using Nwc.XmlRpc; 38using OpenMetaverse; 39using System; 40using System.Collections; 41using System.Collections.Generic; 42using System.Linq; 43using System.Net; 44using System.Threading; 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 79namespace Aurora.Modules.Scripting 80{ 81 public class XMLRPCModule : INonSharedRegionModule, IXMLRPC 82 { 83 private readonly object XMLRPCListLock = new object(); 84 private int RemoteReplyScriptTimeout = 9000; 85 private int RemoteReplyScriptWait = 300; 86 private bool m_httpServerStarted; 87 88 private string m_name = "XMLRPCModule"; 89 90 // <channel id, RPCChannelInfo> 91 private Dictionary<UUID, RPCChannelInfo> m_openChannels; 92 private Dictionary<UUID, SendRemoteDataRequest> m_pendingSRDResponses; 93 private int m_remoteDataPort; 94 95 private Dictionary<UUID, RPCRequestInfo> m_rpcPending; 96 private Dictionary<UUID, RPCRequestInfo> m_rpcPendingResponses; 97 private IScriptModule m_scriptModule; 98 99 #region INonSharedRegionModule Members 100 101 public void Initialise(IConfigSource config) 102 { 103 // We need to create these early because the scripts might be calling 104 // But since this gets called for every region, we need to make sure they 105 // get called only one time (or we lose any open channels) 106 if (null == m_openChannels) 107 { 108 m_openChannels = new Dictionary<UUID, RPCChannelInfo>(); 109 m_rpcPending = new Dictionary<UUID, RPCRequestInfo>(); 110 m_rpcPendingResponses = new Dictionary<UUID, RPCRequestInfo>(); 111 m_pendingSRDResponses = new Dictionary<UUID, SendRemoteDataRequest>(); 112 113 if (config.Configs["XMLRPC"] != null) 114 m_remoteDataPort = config.Configs["XMLRPC"].GetInt("XmlRpcPort", m_remoteDataPort); 115 } 116 } 117 118 public void AddRegion(IScene scene) 119 { 120 scene.RegisterModuleInterface<IXMLRPC>(this); 121 } 122 123 public void RemoveRegion(IScene scene) 124 { 125 scene.UnregisterModuleInterface<IXMLRPC>(this); 126 } 127 128 public void RegionLoaded(IScene scene) 129 { 130 if (IsEnabled() && !m_httpServerStarted) 131 { 132 m_httpServerStarted = true; 133 // Start http server 134 // Attach xmlrpc handlers 135 MainConsole.Instance.Info("[XMLRPC MODULE]: " + 136 "Starting up XMLRPC Server on port " + m_remoteDataPort + 137 " for llRemoteData commands."); 138 IHttpServer httpServer = new BaseHttpServer((uint) m_remoteDataPort, MainServer.Instance.HostName, 139 false); 140 httpServer.AddXmlRPCHandler("llRemoteData", XmlRpcRemoteData); 141 httpServer.Start(); 142 } 143 m_scriptModule = scene.RequestModuleInterface<IScriptModule>(); 144 } 145 146 public Type ReplaceableInterface 147 { 148 get { return null; } 149 } 150 151 public void Close() 152 { 153 } 154 155 public string Name 156 { 157 get { return m_name; } 158 } 159 160 #endregion 161 162 #region IXMLRPC Members 163 164 public int Port 165 { 166 get { return m_remoteDataPort; } 167 } 168 169 public bool IsEnabled() 170 { 171 return (m_remoteDataPort > 0); 172 } 173 174 /********************************************** 175 * OpenXMLRPCChannel 176 * 177 * Generate a UUID channel key and add it and 178 * the prim id to dictionary <channelUUID, primUUID> 179 * 180 * A custom channel key can be proposed. 181 * Otherwise, passing UUID.Zero will generate 182 * and return a random channel 183 * 184 * First check if there is a channel assigned for 185 * this itemID. If there is, then someone called 186 * llOpenRemoteDataChannel twice. Just return the 187 * original channel. Other option is to delete the 188 * current channel and assign a new one. 189 * 190 * ********************************************/ 191 192 public UUID OpenXMLRPCChannel(UUID primID, UUID itemID, UUID channelID) 193 { 194 UUID newChannel = UUID.Zero; 195 196 // This should no longer happen, but the check is reasonable anyway 197 if (null == m_openChannels) 198 { 199 MainConsole.Instance.Warn("[XML RPC MODULE]: Attempt to open channel before initialization is complete"); 200 return newChannel; 201 } 202 203 //Is a dupe? 204#if (!ISWIN) 205 foreach (RPCChannelInfo ci in m_openChannels.Values) 206 { 207 if (ci.GetItemID().Equals(itemID)) 208 { 209 // return the original channel ID for this item 210 newChannel = ci.GetChannelID(); 211 break; 212 } 213 } 214#else 215 foreach (RPCChannelInfo ci in m_openChannels.Values.Where(ci => ci.GetItemID().Equals(itemID))) 216 { 217 // return the original channel ID for this item 218 newChannel = ci.GetChannelID(); 219 break; 220 } 221#endif 222 223 if (newChannel == UUID.Zero) 224 { 225 newChannel = (channelID == UUID.Zero) ? UUID.Random() : channelID; 226 RPCChannelInfo rpcChanInfo = new RPCChannelInfo(primID, itemID, newChannel); 227 lock (XMLRPCListLock) 228 { 229 m_openChannels.Add(newChannel, rpcChanInfo); 230 } 231 } 232 233 //Make sure that the cmd handler thread is running 234 m_scriptModule.PokeThreads(itemID); 235 236 return newChannel; 237 } 238 239 // Delete channels based on itemID 240 // for when a script is deleted 241 public void DeleteChannels(UUID itemID) 242 { 243 if (m_openChannels != null) 244 { 245 ArrayList tmp = new ArrayList(); 246 247 lock (XMLRPCListLock) 248 { 249#if (!ISWIN) 250 foreach (RPCChannelInfo li in m_openChannels.Values) 251 { 252 if (li.GetItemID().Equals(itemID)) 253 { 254 tmp.Add(itemID); 255 } 256 } 257#else 258 foreach (RPCChannelInfo li in m_openChannels.Values.Where(li => li.GetItemID().Equals(itemID))) 259 { 260 tmp.Add(itemID); 261 } 262#endif 263 264 IEnumerator tmpEnumerator = tmp.GetEnumerator(); 265 while (tmpEnumerator.MoveNext()) 266 m_openChannels.Remove((UUID) tmpEnumerator.Current); 267 } 268 } 269 270 //Make sure that the cmd handler thread is running 271 m_scriptModule.PokeThreads(itemID); 272 } 273 274 /********************************************** 275 * Remote Data Reply 276 * 277 * Response to RPC message 278 * 279 *********************************************/ 280 281 public void RemoteDataReply(string channel, string message_id, string sdata, int idata) 282 { 283 UUID message_key = new UUID(message_id); 284 UUID channel_key = new UUID(channel); 285 286 RPCRequestInfo rpcInfo = null; 287 288 if (message_key == UUID.Zero) 289 { 290#if (!ISWIN) 291 foreach (RPCRequestInfo oneRpcInfo in m_rpcPendingResponses.Values) 292 { 293 if (oneRpcInfo.GetChannelKey() == channel_key) rpcInfo = oneRpcInfo; 294 } 295#else 296 foreach ( 297 RPCRequestInfo oneRpcInfo in 298 m_rpcPendingResponses.Values.Where(oneRpcInfo => oneRpcInfo.GetChannelKey() == channel_key)) 299 rpcInfo = oneRpcInfo; 300#endif 301 } 302 else 303 { 304 m_rpcPendingResponses.TryGetValue(message_key, out rpcInfo); 305 } 306 307 if (rpcInfo != null) 308 { 309 rpcInfo.SetStrRetval(sdata); 310 rpcInfo.SetIntRetval(idata); 311 rpcInfo.SetProcessed(true); 312 m_rpcPendingResponses.Remove(message_key); 313 314 //Make sure that the cmd handler thread is running 315 m_scriptModule.PokeThreads(rpcInfo.GetItemID()); 316 } 317 else 318 { 319 MainConsole.Instance.Warn("[XML RPC MODULE]: Channel or message_id not found"); 320 } 321 } 322 323 /********************************************** 324 * CloseXMLRPCChannel 325 * 326 * Remove channel from dictionary 327 * 328 *********************************************/ 329 330 public void CloseXMLRPCChannel(UUID channelKey) 331 { 332 if (m_openChannels.ContainsKey(channelKey)) 333 m_openChannels.Remove(channelKey); 334 } 335 336 337 public bool hasRequests() 338 { 339 lock (XMLRPCListLock) 340 { 341 if (m_rpcPending != null) 342 if (m_rpcPending.Count > 0) 343 return true; 344 if (m_pendingSRDResponses != null) 345 if (m_pendingSRDResponses.Count > 0) 346 return true; 347 return false; 348 } 349 } 350 351 public IXmlRpcRequestInfo GetNextCompletedRequest() 352 { 353 if (m_rpcPending != null) 354 { 355 if (m_rpcPending.Count == 0) 356 return null; 357 lock (XMLRPCListLock) 358 { 359#if (!ISWIN) 360 foreach (RPCRequestInfo luid in m_rpcPending.Values) 361 { 362 if (!luid.IsProcessed()) 363 { 364 return luid; 365 } 366 } 367#else 368 foreach (RPCRequestInfo luid in m_rpcPending.Values.Where(luid => !luid.IsProcessed())) 369 { 370 return luid; 371 } 372#endif 373 } 374 } 375 return null; 376 } 377 378 public void RemoveCompletedRequest(UUID id) 379 { 380 lock (XMLRPCListLock) 381 { 382 RPCRequestInfo tmp; 383 if (m_rpcPending.TryGetValue(id, out tmp)) 384 { 385 m_rpcPending.Remove(id); 386 m_rpcPendingResponses.Add(id, tmp); 387 } 388 else 389 { 390 MainConsole.Instance.Error("[XML RPC MODULE]: UNABLE TO REMOVE COMPLETED REQUEST"); 391 } 392 } 393 } 394 395 public UUID SendRemoteData(UUID primID, UUID itemID, string channel, string dest, int idata, string sdata) 396 { 397 SendRemoteDataRequest req = new SendRemoteDataRequest( 398 primID, itemID, channel, dest, idata, sdata 399 ); 400 m_pendingSRDResponses.Add(req.GetReqID(), req); 401 req.Process(); 402 403 //Make sure that the cmd handler thread is running 404 m_scriptModule.PokeThreads(itemID); 405 return req.ReqID; 406 } 407 408 public IServiceRequest GetNextCompletedSRDRequest() 409 { 410 if (m_pendingSRDResponses != null) 411 { 412 if (m_pendingSRDResponses.Count == 0) 413 return null; 414 lock (XMLRPCListLock) 415 { 416#if (!ISWIN) 417 foreach (SendRemoteDataRequest luid in m_pendingSRDResponses.Values) 418 { 419 if (luid.Finished) 420 { 421 return luid; 422 } 423 } 424#else 425 foreach (SendRemoteDataRequest luid in m_pendingSRDResponses.Values.Where(luid => luid.Finished)) 426 { 427 return luid; 428 } 429#endif 430 } 431 } 432 return null; 433 } 434 435 public void RemoveCompletedSRDRequest(UUID id) 436 { 437 lock (XMLRPCListLock) 438 { 439 SendRemoteDataRequest tmpReq; 440 if (m_pendingSRDResponses.TryGetValue(id, out tmpReq)) 441 { 442 m_pendingSRDResponses.Remove(id); 443 } 444 } 445 } 446 447 public void CancelSRDRequests(UUID itemID) 448 { 449 if (m_pendingSRDResponses != null) 450 { 451 lock (XMLRPCListLock) 452 { 453#if (!ISWIN) 454 foreach (SendRemoteDataRequest li in m_pendingSRDResponses.Values) 455 { 456 if (li.ItemID.Equals(itemID)) 457 { 458 m_pendingSRDResponses.Remove(li.GetReqID()); 459 } 460 } 461#else 462 foreach ( 463 SendRemoteDataRequest li in m_pendingSRDResponses.Values.Where(li => li.ItemID.Equals(itemID))) 464 { 465 m_pendingSRDResponses.Remove(li.GetReqID()); 466 } 467#endif 468 } 469 } 470 } 471 472 #endregion 473 474 public XmlRpcResponse XmlRpcRemoteData(XmlRpcRequest request, IPEndPoint remoteClient) 475 { 476 XmlRpcResponse response = new XmlRpcResponse(); 477 478 Hashtable requestData = (Hashtable) request.Params[0]; 479 bool GoodXML = (requestData.Contains("Channel") && requestData.Contains("IntValue") && 480 requestData.Contains("StringValue")); 481 482 if (GoodXML) 483 { 484 UUID channel = new UUID((string) requestData["Channel"]); 485 RPCChannelInfo rpcChanInfo; 486 if (m_openChannels.TryGetValue(channel, out rpcChanInfo)) 487 { 488 string intVal = Convert.ToInt32(requestData["IntValue"]).ToString(); 489 string strVal = (string) requestData["StringValue"]; 490 491 RPCRequestInfo rpcInfo; 492 493 lock (XMLRPCListLock) 494 { 495 rpcInfo = 496 new RPCRequestInfo(rpcChanInfo.GetPrimID(), rpcChanInfo.GetItemID(), channel, strVal, 497 intVal); 498 m_rpcPending.Add(rpcInfo.GetMessageID(), rpcInfo); 499 } 500 501 int timeoutCtr = 0; 502 503 while (!rpcInfo.IsProcessed() && (timeoutCtr < RemoteReplyScriptTimeout)) 504 { 505 Thread.Sleep(RemoteReplyScriptWait); 506 timeoutCtr += RemoteReplyScriptWait; 507 } 508 if (rpcInfo.IsProcessed()) 509 { 510 Hashtable param = new Hashtable(); 511 param["StringValue"] = rpcInfo.GetStrRetval(); 512 param["IntValue"] = rpcInfo.GetIntRetval(); 513 514 ArrayList parameters = new ArrayList {param}; 515 516 response.Value = parameters; 517 rpcInfo = null; 518 } 519 else 520 { 521 response.SetFault(-1, "Script timeout"); 522 rpcInfo = null; 523 } 524 } 525 else 526 { 527 response.SetFault(-1, "Invalid channel"); 528 } 529 } 530 531 //Make sure that the cmd handler thread is running 532 m_scriptModule.PokeThreads(UUID.Zero); 533 534 return response; 535 } 536 } 537 538 public class RPCRequestInfo : IXmlRpcRequestInfo 539 { 540 private readonly UUID m_ChannelKey; 541 private readonly string m_IntVal; 542 private readonly UUID m_ItemID; 543 private readonly UUID m_MessageID; 544 private readonly UUID m_PrimID; 545 private readonly string m_StrVal; 546 private bool m_processed; 547 private int m_respInt; 548 private string m_respStr; 549 550 public RPCRequestInfo(UUID primID, UUID itemID, UUID channelKey, string strVal, string intVal) 551 { 552 m_PrimID = primID; 553 m_StrVal = strVal; 554 m_IntVal = intVal; 555 m_ItemID = itemID; 556 m_ChannelKey = channelKey; 557 m_MessageID = UUID.Random(); 558 m_processed = false; 559 m_respStr = String.Empty; 560 m_respInt = 0; 561 } 562 563 #region IXmlRpcRequestInfo Members 564 565 public bool IsProcessed() 566 { 567 return m_processed; 568 } 569 570 public UUID GetChannelKey() 571 { 572 return m_ChannelKey; 573 } 574 575 public void SetProcessed(bool processed) 576 { 577 m_processed = processed; 578 } 579 580 public void SetStrRetval(string resp) 581 { 582 m_respStr = resp; 583 } 584 585 public string GetStrRetval() 586 { 587 return m_respStr; 588 } 589 590 public void SetIntRetval(int resp) 591 { 592 m_respInt = resp; 593 } 594 595 public int GetIntRetval() 596 { 597 return m_respInt; 598 } 599 600 public UUID GetPrimID() 601 { 602 return m_PrimID; 603 } 604 605 public UUID GetItemID() 606 { 607 return m_ItemID; 608 } 609 610 public string GetStrVal() 611 { 612 return m_StrVal; 613 } 614 615 public int GetIntValue() 616 { 617 return int.Parse(m_IntVal); 618 } 619 620 public UUID GetMessageID() 621 { 622 return m_MessageID; 623 } 624 625 #endregion 626 } 627 628 public class RPCChannelInfo 629 { 630 private readonly UUID m_ChannelKey; 631 private readonly UUID m_itemID; 632 private readonly UUID m_primID; 633 634 public RPCChannelInfo(UUID primID, UUID itemID, UUID channelID) 635 { 636 m_ChannelKey = channelID; 637 m_primID = primID; 638 m_itemID = itemID; 639 } 640 641 public UUID GetItemID() 642 { 643 return m_itemID; 644 } 645 646 public UUID GetChannelID() 647 { 648 return m_ChannelKey; 649 } 650 651 public UUID GetPrimID() 652 { 653 return m_primID; 654 } 655 } 656 657 public class SendRemoteDataRequest : ISendRemoteDataRequest 658 { 659 public string Channel { get; set; } 660 public string DestURL { get; set; } 661 public int Idata { get; set; } 662 663 public XmlRpcRequest Request { get; set; } 664 public int ResponseIdata { get; set; } 665 public string ResponseSdata { get; set; } 666 public string Sdata { get; set; } 667 private bool _finished; 668 private Thread httpThread; 669 670 public SendRemoteDataRequest(UUID primID, UUID itemID, string channel, string dest, int idata, string sdata) 671 { 672 this.Channel = channel; 673 DestURL = dest; 674 this.Idata = idata; 675 this.Sdata = sdata; 676 ItemID = itemID; 677 PrimID = primID; 678 679 ReqID = UUID.Random(); 680 } 681 682 #region IServiceRequest Members 683 684 public bool Finished 685 { 686 get { return _finished; } 687 set { _finished = value; } 688 } 689 690 public UUID ItemID { get; set; } 691 692 public UUID PrimID { get; set; } 693 694 public UUID ReqID { get; set; } 695 696 public void Process() 697 { 698 httpThread = new Thread(SendRequest) 699 {Name = "HttpRequestThread", Priority = ThreadPriority.BelowNormal, IsBackground = true}; 700 _finished = false; 701 httpThread.Start(); 702 } 703 704 /* 705 * TODO: More work on the response codes. Right now 706 * returning 200 for success or 499 for exception 707 */ 708 709 public void SendRequest() 710 { 711 Culture.SetCurrentCulture(); 712 Hashtable param = new Hashtable(); 713 714 // Check if channel is an UUID 715 // if not, use as method name 716 UUID parseUID; 717 string mName = "llRemoteData"; 718 if (!string.IsNullOrEmpty(Channel)) 719 if (!UUID.TryParse(Channel, out parseUID)) 720 mName = Channel; 721 else 722 param["Channel"] = Channel; 723 724 param["StringValue"] = Sdata; 725 param["IntValue"] = Convert.ToString(Idata); 726 727 ArrayList parameters = new ArrayList {param}; 728 XmlRpcRequest req = new XmlRpcRequest(mName, parameters); 729 try 730 { 731 XmlRpcResponse resp = req.Send(DestURL, 30000); 732 if (resp != null) 733 { 734 Hashtable respParms; 735 if (resp.Value.GetType().Equals(typeof (Hashtable))) 736 { 737 respParms = (Hashtable) resp.Value; 738 } 739 else 740 { 741 ArrayList respData = (ArrayList) resp.Value; 742 respParms = (Hashtable) respData[0]; 743 } 744 if (respParms != null) 745 { 746 if (respParms.Contains("StringValue")) 747 { 748 Sdata = (string) respParms["StringValue"]; 749 } 750 if (respParms.Contains("IntValue")) 751 { 752 Idata = Convert.ToInt32(respParms["IntValue"]); 753 } 754 if (respParms.Contains("faultString")) 755 { 756 Sdata = (string) respParms["faultString"]; 757 } 758 if (respParms.Contains("faultCode")) 759 { 760 Idata = Convert.ToInt32(respParms["faultCode"]); 761 } 762 } 763 } 764 } 765 catch (Exception we) 766 { 767 Sdata = we.Message; 768 MainConsole.Instance.Warn("[SendRemoteDataRequest]: Request failed"); 769 MainConsole.Instance.Warn(we.StackTrace); 770 } 771 772 _finished = true; 773 } 774 775 public void Stop() 776 { 777 try 778 { 779 httpThread.Abort(); 780 } 781 catch (Exception) 782 { 783 } 784 } 785 786 #endregion 787 788 public UUID GetReqID() 789 { 790 return ReqID; 791 } 792 } 793}