514 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
			
		
		
	
	
			514 lines
		
	
	
		
			16 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 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.Xml;
 | |
| using System.Collections;
 | |
| using System.Collections.Generic;
 | |
| using System.Diagnostics;
 | |
| using System.Reflection;
 | |
| using System.Text;
 | |
| using System.Text.RegularExpressions;
 | |
| using System.Threading;
 | |
| using OpenMetaverse;
 | |
| using Nini.Config;
 | |
| using OpenSim.Framework.Servers.HttpServer;
 | |
| using log4net;
 | |
| 
 | |
| namespace OpenSim.Framework.Console
 | |
| {
 | |
|     public class ConsoleConnection
 | |
|     {
 | |
|         public int last;
 | |
|         public long lastLineSeen;
 | |
|         public bool newConnection = true;
 | |
|     }
 | |
| 
 | |
|     // A console that uses REST interfaces
 | |
|     //
 | |
|     public class RemoteConsole : CommandConsole
 | |
|     {
 | |
|         private IHttpServer m_Server = null;
 | |
|         private IConfigSource m_Config = null;
 | |
| 
 | |
|         private List<string> m_Scrollback = new List<string>();
 | |
|         private ManualResetEvent m_DataEvent = new ManualResetEvent(false);
 | |
|         private List<string> m_InputData = new List<string>();
 | |
|         private long m_LineNumber = 0;
 | |
|         private Dictionary<UUID, ConsoleConnection> m_Connections =
 | |
|                 new Dictionary<UUID, ConsoleConnection>();
 | |
|         private string m_UserName = String.Empty;
 | |
|         private string m_Password = String.Empty;
 | |
|         private string m_AllowedOrigin = String.Empty;
 | |
| 
 | |
|         public RemoteConsole(string defaultPrompt) : base(defaultPrompt)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         public void ReadConfig(IConfigSource config)
 | |
|         {
 | |
|             m_Config = config;
 | |
| 
 | |
|             IConfig netConfig = m_Config.Configs["Network"];
 | |
|             if (netConfig == null)
 | |
|                 return;
 | |
| 
 | |
|             m_UserName = netConfig.GetString("ConsoleUser", String.Empty);
 | |
|             m_Password = netConfig.GetString("ConsolePass", String.Empty);
 | |
|             m_AllowedOrigin = netConfig.GetString("ConsoleAllowedOrigin", String.Empty);
 | |
|         }
 | |
| 
 | |
|         public void SetServer(IHttpServer server)
 | |
|         {
 | |
|             m_Server = server;
 | |
| 
 | |
|             m_Server.AddHTTPHandler("/StartSession/", HandleHttpStartSession);
 | |
|             m_Server.AddHTTPHandler("/CloseSession/", HandleHttpCloseSession);
 | |
|             m_Server.AddHTTPHandler("/SessionCommand/", HandleHttpSessionCommand);
 | |
|         }
 | |
| 
 | |
|         public override void Output(string text, string level)
 | |
|         {
 | |
|             lock (m_Scrollback)
 | |
|             {
 | |
|                 while (m_Scrollback.Count >= 1000)
 | |
|                     m_Scrollback.RemoveAt(0);
 | |
|                 m_LineNumber++;
 | |
|                 m_Scrollback.Add(String.Format("{0}", m_LineNumber)+":"+level+":"+text);
 | |
|             }
 | |
|             FireOnOutput(text.Trim());
 | |
|             System.Console.WriteLine(text.Trim());
 | |
|         }
 | |
| 
 | |
|         public override void Output(string text)
 | |
|         {
 | |
|             Output(text, "normal");
 | |
|         }
 | |
| 
 | |
|         public override string ReadLine(string p, bool isCommand, bool e)
 | |
|         {
 | |
|             if (isCommand)
 | |
|                 Output("+++"+p);
 | |
|             else
 | |
|                 Output("-++"+p);
 | |
| 
 | |
|             m_DataEvent.WaitOne();
 | |
| 
 | |
|             string cmdinput;
 | |
| 
 | |
|             lock (m_InputData)
 | |
|             {
 | |
|                 if (m_InputData.Count == 0)
 | |
|                 {
 | |
|                     m_DataEvent.Reset();
 | |
|                     return "";
 | |
|                 }
 | |
| 
 | |
|                 cmdinput = m_InputData[0];
 | |
|                 m_InputData.RemoveAt(0);
 | |
|                 if (m_InputData.Count == 0)
 | |
|                     m_DataEvent.Reset();
 | |
| 
 | |
|             }
 | |
| 
 | |
|             if (isCommand)
 | |
|             {
 | |
|                 string[] cmd = Commands.Resolve(Parser.Parse(cmdinput));
 | |
| 
 | |
|                 if (cmd.Length != 0)
 | |
|                 {
 | |
|                     int i;
 | |
| 
 | |
|                     for (i=0 ; i < cmd.Length ; i++)
 | |
|                     {
 | |
|                         if (cmd[i].Contains(" "))
 | |
|                             cmd[i] = "\"" + cmd[i] + "\"";
 | |
|                     }
 | |
|                     return String.Empty;
 | |
|                 }
 | |
|             }
 | |
|             return cmdinput;
 | |
|         }
 | |
| 
 | |
|         private Hashtable CheckOrigin(Hashtable result)
 | |
|         {
 | |
|             if (!string.IsNullOrEmpty(m_AllowedOrigin))
 | |
|                 result["access_control_allow_origin"] = m_AllowedOrigin;
 | |
|             return result;
 | |
|         }
 | |
|         /* TODO: Figure out how PollServiceHTTPHandler can access the request headers
 | |
|          * in order to use m_AllowedOrigin as a regular expression
 | |
|         private Hashtable CheckOrigin(Hashtable headers, Hashtable result)
 | |
|         {
 | |
|             if (!string.IsNullOrEmpty(m_AllowedOrigin))
 | |
|             {
 | |
|                 if (headers.ContainsKey("origin"))
 | |
|                 {
 | |
|                     string origin = headers["origin"].ToString();
 | |
|                     if (Regex.IsMatch(origin, m_AllowedOrigin))
 | |
|                         result["access_control_allow_origin"] = origin;
 | |
|                 }
 | |
|             }
 | |
|             return result;
 | |
|         }
 | |
|         */
 | |
| 
 | |
|         private void DoExpire()
 | |
|         {
 | |
|             List<UUID> expired = new List<UUID>();
 | |
| 
 | |
|             lock (m_Connections)
 | |
|             {
 | |
|                 foreach (KeyValuePair<UUID, ConsoleConnection> kvp in m_Connections)
 | |
|                 {
 | |
|                     if (System.Environment.TickCount - kvp.Value.last > 500000)
 | |
|                         expired.Add(kvp.Key);
 | |
|                 }
 | |
| 
 | |
|                 foreach (UUID id in expired)
 | |
|                 {
 | |
|                     m_Connections.Remove(id);
 | |
|                     CloseConnection(id);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private Hashtable HandleHttpStartSession(Hashtable request)
 | |
|         {
 | |
|             DoExpire();
 | |
| 
 | |
|             Hashtable post = DecodePostString(request["body"].ToString());
 | |
|             Hashtable reply = new Hashtable();
 | |
| 
 | |
|             reply["str_response_string"] = "";
 | |
|             reply["int_response_code"] = 401;
 | |
|             reply["content_type"] = "text/plain";
 | |
| 
 | |
|             if (m_UserName == String.Empty)
 | |
|                 return reply;
 | |
| 
 | |
|             if (post["USER"] == null || post["PASS"] == null)
 | |
|                 return reply;
 | |
| 
 | |
|             if (m_UserName != post["USER"].ToString() ||
 | |
|                 m_Password != post["PASS"].ToString())
 | |
|             {
 | |
|                 return reply;
 | |
|             }
 | |
| 
 | |
|             ConsoleConnection c = new ConsoleConnection();
 | |
|             c.last = System.Environment.TickCount;
 | |
|             c.lastLineSeen = 0;
 | |
| 
 | |
|             UUID sessionID = UUID.Random();
 | |
| 
 | |
|             lock (m_Connections)
 | |
|             {
 | |
|                 m_Connections[sessionID] = c;
 | |
|             }
 | |
| 
 | |
|             string uri = "/ReadResponses/" + sessionID.ToString() + "/";
 | |
| 
 | |
|             m_Server.AddPollServiceHTTPHandler(
 | |
|                 uri, new PollServiceEventArgs(null, HasEvents, GetEvents, NoEvents, sessionID,25000)); // 25 secs timeout
 | |
| 
 | |
|             XmlDocument xmldoc = new XmlDocument();
 | |
|             XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
 | |
|                     "", "");
 | |
| 
 | |
|             xmldoc.AppendChild(xmlnode);
 | |
|             XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
 | |
|                     "");
 | |
| 
 | |
|             xmldoc.AppendChild(rootElement);
 | |
| 
 | |
|             XmlElement id = xmldoc.CreateElement("", "SessionID", "");
 | |
|             id.AppendChild(xmldoc.CreateTextNode(sessionID.ToString()));
 | |
| 
 | |
|             rootElement.AppendChild(id);
 | |
| 
 | |
|             XmlElement prompt = xmldoc.CreateElement("", "Prompt", "");
 | |
|             prompt.AppendChild(xmldoc.CreateTextNode(DefaultPrompt));
 | |
| 
 | |
|             rootElement.AppendChild(prompt);
 | |
| 
 | |
|             rootElement.AppendChild(MainConsole.Instance.Commands.GetXml(xmldoc));
 | |
| 
 | |
|             reply["str_response_string"] = xmldoc.InnerXml;
 | |
|             reply["int_response_code"] = 200;
 | |
|             reply["content_type"] = "text/xml";
 | |
|             reply = CheckOrigin(reply);
 | |
| 
 | |
|             return reply;
 | |
|         }
 | |
| 
 | |
|         private Hashtable HandleHttpCloseSession(Hashtable request)
 | |
|         {
 | |
|             DoExpire();
 | |
| 
 | |
|             Hashtable post = DecodePostString(request["body"].ToString());
 | |
|             Hashtable reply = new Hashtable();
 | |
| 
 | |
|             reply["str_response_string"] = "";
 | |
|             reply["int_response_code"] = 404;
 | |
|             reply["content_type"] = "text/plain";
 | |
| 
 | |
|             if (post["ID"] == null)
 | |
|                 return reply;
 | |
| 
 | |
|             UUID id;
 | |
|             if (!UUID.TryParse(post["ID"].ToString(), out id))
 | |
|                 return reply;
 | |
| 
 | |
|             lock (m_Connections)
 | |
|             {
 | |
|                 if (m_Connections.ContainsKey(id))
 | |
|                 {
 | |
|                     m_Connections.Remove(id);
 | |
|                     CloseConnection(id);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             XmlDocument xmldoc = new XmlDocument();
 | |
|             XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
 | |
|                     "", "");
 | |
| 
 | |
|             xmldoc.AppendChild(xmlnode);
 | |
|             XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
 | |
|                     "");
 | |
| 
 | |
|             xmldoc.AppendChild(rootElement);
 | |
| 
 | |
|             XmlElement res = xmldoc.CreateElement("", "Result", "");
 | |
|             res.AppendChild(xmldoc.CreateTextNode("OK"));
 | |
| 
 | |
|             rootElement.AppendChild(res);
 | |
| 
 | |
|             reply["str_response_string"] = xmldoc.InnerXml;
 | |
|             reply["int_response_code"] = 200;
 | |
|             reply["content_type"] = "text/xml";
 | |
|             reply = CheckOrigin(reply);
 | |
| 
 | |
|             return reply;
 | |
|         }
 | |
| 
 | |
|         private Hashtable HandleHttpSessionCommand(Hashtable request)
 | |
|         {
 | |
|             DoExpire();
 | |
| 
 | |
|             Hashtable post = DecodePostString(request["body"].ToString());
 | |
|             Hashtable reply = new Hashtable();
 | |
| 
 | |
|             reply["str_response_string"] = "";
 | |
|             reply["int_response_code"] = 404;
 | |
|             reply["content_type"] = "text/plain";
 | |
| 
 | |
|             if (post["ID"] == null)
 | |
|                 return reply;
 | |
| 
 | |
|             UUID id;
 | |
|             if (!UUID.TryParse(post["ID"].ToString(), out id))
 | |
|                 return reply;
 | |
| 
 | |
|             lock (m_Connections)
 | |
|             {
 | |
|                 if (!m_Connections.ContainsKey(id))
 | |
|                     return reply;
 | |
|             }
 | |
| 
 | |
|             if (post["COMMAND"] == null)
 | |
|                 return reply;
 | |
| 
 | |
|             lock (m_InputData)
 | |
|             {
 | |
|                 m_DataEvent.Set();
 | |
|                 m_InputData.Add(post["COMMAND"].ToString());
 | |
|             }
 | |
| 
 | |
|             XmlDocument xmldoc = new XmlDocument();
 | |
|             XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
 | |
|                     "", "");
 | |
| 
 | |
|             xmldoc.AppendChild(xmlnode);
 | |
|             XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
 | |
|                     "");
 | |
| 
 | |
|             xmldoc.AppendChild(rootElement);
 | |
| 
 | |
|             XmlElement res = xmldoc.CreateElement("", "Result", "");
 | |
|             res.AppendChild(xmldoc.CreateTextNode("OK"));
 | |
| 
 | |
|             rootElement.AppendChild(res);
 | |
| 
 | |
|             reply["str_response_string"] = xmldoc.InnerXml;
 | |
|             reply["int_response_code"] = 200;
 | |
|             reply["content_type"] = "text/xml";
 | |
|             reply = CheckOrigin(reply);
 | |
| 
 | |
|             return reply;
 | |
|         }
 | |
| 
 | |
|         private Hashtable DecodePostString(string data)
 | |
|         {
 | |
|             Hashtable result = new Hashtable();
 | |
| 
 | |
|             string[] terms = data.Split(new char[] {'&'});
 | |
| 
 | |
|             foreach (string term in terms)
 | |
|             {
 | |
|                 string[] elems = term.Split(new char[] {'='});
 | |
|                 if (elems.Length == 0)
 | |
|                     continue;
 | |
| 
 | |
|                 string name = System.Web.HttpUtility.UrlDecode(elems[0]);
 | |
|                 string value = String.Empty;
 | |
| 
 | |
|                 if (elems.Length > 1)
 | |
|                     value = System.Web.HttpUtility.UrlDecode(elems[1]);
 | |
|                 
 | |
|                 result[name] = value;
 | |
|             }
 | |
| 
 | |
|             return result;
 | |
|         }
 | |
| 
 | |
|         public void CloseConnection(UUID id)
 | |
|         {
 | |
|             try
 | |
|             {
 | |
|                 string uri = "/ReadResponses/" + id.ToString() + "/";
 | |
| 
 | |
|                 m_Server.RemovePollServiceHTTPHandler("", uri);
 | |
|             }
 | |
|             catch (Exception)
 | |
|             {
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private bool HasEvents(UUID RequestID, UUID sessionID)
 | |
|         {
 | |
|             ConsoleConnection c = null;
 | |
| 
 | |
|             lock (m_Connections)
 | |
|             {
 | |
|                 if (!m_Connections.ContainsKey(sessionID))
 | |
|                     return false;
 | |
|                 c = m_Connections[sessionID];
 | |
|             }
 | |
|             c.last = System.Environment.TickCount;
 | |
|             if (c.lastLineSeen < m_LineNumber)
 | |
|                 return true;
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         private Hashtable GetEvents(UUID RequestID, UUID sessionID)
 | |
|         {
 | |
|             ConsoleConnection c = null;
 | |
| 
 | |
|             lock (m_Connections)
 | |
|             {
 | |
|                 if (!m_Connections.ContainsKey(sessionID))
 | |
|                     return NoEvents(RequestID, UUID.Zero);
 | |
|                 c = m_Connections[sessionID];
 | |
|             }
 | |
|             c.last = System.Environment.TickCount;
 | |
|             if (c.lastLineSeen >= m_LineNumber)
 | |
|                 return NoEvents(RequestID, UUID.Zero);
 | |
| 
 | |
|             Hashtable result = new Hashtable();
 | |
| 
 | |
|             XmlDocument xmldoc = new XmlDocument();
 | |
|             XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
 | |
|                     "", "");
 | |
| 
 | |
|             xmldoc.AppendChild(xmlnode);
 | |
|             XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
 | |
|                     "");
 | |
| 
 | |
|             if (c.newConnection)
 | |
|             {
 | |
|                 c.newConnection = false;
 | |
|                 Output("+++" + DefaultPrompt);
 | |
|             }
 | |
| 
 | |
|             lock (m_Scrollback)
 | |
|             {
 | |
|                 long startLine = m_LineNumber - m_Scrollback.Count;
 | |
|                 long sendStart = startLine;
 | |
|                 if (sendStart < c.lastLineSeen)
 | |
|                     sendStart = c.lastLineSeen;
 | |
| 
 | |
|                 for (long i = sendStart ; i < m_LineNumber ; i++)
 | |
|                 {
 | |
|                     XmlElement res = xmldoc.CreateElement("", "Line", "");
 | |
|                     long line = i + 1;
 | |
|                     res.SetAttribute("Number", line.ToString());
 | |
|                     res.AppendChild(xmldoc.CreateTextNode(m_Scrollback[(int)(i - startLine)]));
 | |
| 
 | |
|                     rootElement.AppendChild(res);
 | |
|                 }
 | |
|             }
 | |
|             c.lastLineSeen = m_LineNumber;
 | |
| 
 | |
|             xmldoc.AppendChild(rootElement);
 | |
| 
 | |
|             result["str_response_string"] = xmldoc.InnerXml;
 | |
|             result["int_response_code"] = 200;
 | |
|             result["content_type"] = "application/xml";
 | |
|             result["keepalive"] = false;
 | |
|             result["reusecontext"] = false;
 | |
|             result = CheckOrigin(result);
 | |
| 
 | |
|             return result;
 | |
|         }
 | |
| 
 | |
|         private Hashtable NoEvents(UUID RequestID, UUID id)
 | |
|         {
 | |
|             Hashtable result = new Hashtable();
 | |
| 
 | |
|             XmlDocument xmldoc = new XmlDocument();
 | |
|             XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
 | |
|                     "", "");
 | |
| 
 | |
|             xmldoc.AppendChild(xmlnode);
 | |
|             XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
 | |
|                     "");
 | |
| 
 | |
|             xmldoc.AppendChild(rootElement);
 | |
| 
 | |
|             result["str_response_string"] = xmldoc.InnerXml;
 | |
|             result["int_response_code"] = 200;
 | |
|             result["content_type"] = "text/xml";
 | |
|             result["keepalive"] = false;
 | |
|             result["reusecontext"] = false;
 | |
|             result = CheckOrigin(result);
 | |
| 
 | |
|             return result;
 | |
|         }
 | |
|     }
 | |
| }
 |