OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
RemoteConsole.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.Xml;
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Diagnostics;
33 using System.Reflection;
34 using System.Text;
35 using System.Text.RegularExpressions;
36 using System.Threading;
37 using OpenMetaverse;
38 using Nini.Config;
39 using OpenSim.Framework.Servers.HttpServer;
40 using log4net;
41 
42 namespace OpenSim.Framework.Console
43 {
44  public class ConsoleConnection
45  {
46  public int last;
47  public long lastLineSeen;
48  public bool newConnection = true;
49  }
50 
51  // A console that uses REST interfaces
52  //
54  {
55  private IHttpServer m_Server = null;
56  private IConfigSource m_Config = null;
57 
58  private List<string> m_Scrollback = new List<string>();
59  private ManualResetEvent m_DataEvent = new ManualResetEvent(false);
60  private List<string> m_InputData = new List<string>();
61  private long m_LineNumber = 0;
62  private Dictionary<UUID, ConsoleConnection> m_Connections =
63  new Dictionary<UUID, ConsoleConnection>();
64  private string m_UserName = String.Empty;
65  private string m_Password = String.Empty;
66  private string m_AllowedOrigin = String.Empty;
67 
68  public RemoteConsole(string defaultPrompt) : base(defaultPrompt)
69  {
70  }
71 
72  public void ReadConfig(IConfigSource config)
73  {
74  m_Config = config;
75 
76  IConfig netConfig = m_Config.Configs["Network"];
77  if (netConfig == null)
78  return;
79 
80  m_UserName = netConfig.GetString("ConsoleUser", String.Empty);
81  m_Password = netConfig.GetString("ConsolePass", String.Empty);
82  m_AllowedOrigin = netConfig.GetString("ConsoleAllowedOrigin", String.Empty);
83  }
84 
85  public void SetServer(IHttpServer server)
86  {
87  m_Server = server;
88 
89  m_Server.AddHTTPHandler("/StartSession/", HandleHttpStartSession);
90  m_Server.AddHTTPHandler("/CloseSession/", HandleHttpCloseSession);
91  m_Server.AddHTTPHandler("/SessionCommand/", HandleHttpSessionCommand);
92  }
93 
94  public override void Output(string text, string level)
95  {
96  lock (m_Scrollback)
97  {
98  while (m_Scrollback.Count >= 1000)
99  m_Scrollback.RemoveAt(0);
100  m_LineNumber++;
101  m_Scrollback.Add(String.Format("{0}", m_LineNumber)+":"+level+":"+text);
102  }
103  FireOnOutput(text.Trim());
104  System.Console.WriteLine(text.Trim());
105  }
106 
107  public override void Output(string text)
108  {
109  Output(text, "normal");
110  }
111 
112  public override string ReadLine(string p, bool isCommand, bool e)
113  {
114  if (isCommand)
115  Output("+++"+p);
116  else
117  Output("-++"+p);
118 
119  m_DataEvent.WaitOne();
120 
121  string cmdinput;
122 
123  lock (m_InputData)
124  {
125  if (m_InputData.Count == 0)
126  {
127  m_DataEvent.Reset();
128  return "";
129  }
130 
131  cmdinput = m_InputData[0];
132  m_InputData.RemoveAt(0);
133  if (m_InputData.Count == 0)
134  m_DataEvent.Reset();
135 
136  }
137 
138  if (isCommand)
139  {
140  string[] cmd = Commands.Resolve(Parser.Parse(cmdinput));
141 
142  if (cmd.Length != 0)
143  {
144  int i;
145 
146  for (i=0 ; i < cmd.Length ; i++)
147  {
148  if (cmd[i].Contains(" "))
149  cmd[i] = "\"" + cmd[i] + "\"";
150  }
151  return String.Empty;
152  }
153  }
154  return cmdinput;
155  }
156 
157  private Hashtable CheckOrigin(Hashtable result)
158  {
159  if (!string.IsNullOrEmpty(m_AllowedOrigin))
160  result["access_control_allow_origin"] = m_AllowedOrigin;
161  return result;
162  }
163  /* TODO: Figure out how PollServiceHTTPHandler can access the request headers
164  * in order to use m_AllowedOrigin as a regular expression
165  private Hashtable CheckOrigin(Hashtable headers, Hashtable result)
166  {
167  if (!string.IsNullOrEmpty(m_AllowedOrigin))
168  {
169  if (headers.ContainsKey("origin"))
170  {
171  string origin = headers["origin"].ToString();
172  if (Regex.IsMatch(origin, m_AllowedOrigin))
173  result["access_control_allow_origin"] = origin;
174  }
175  }
176  return result;
177  }
178  */
179 
180  private void DoExpire()
181  {
182  List<UUID> expired = new List<UUID>();
183 
184  lock (m_Connections)
185  {
186  foreach (KeyValuePair<UUID, ConsoleConnection> kvp in m_Connections)
187  {
188  if (System.Environment.TickCount - kvp.Value.last > 500000)
189  expired.Add(kvp.Key);
190  }
191 
192  foreach (UUID id in expired)
193  {
194  m_Connections.Remove(id);
195  CloseConnection(id);
196  }
197  }
198  }
199 
200  private Hashtable HandleHttpStartSession(Hashtable request)
201  {
202  DoExpire();
203 
204  Hashtable post = DecodePostString(request["body"].ToString());
205  Hashtable reply = new Hashtable();
206 
207  reply["str_response_string"] = "";
208  reply["int_response_code"] = 401;
209  reply["content_type"] = "text/plain";
210 
211  if (m_UserName == String.Empty)
212  return reply;
213 
214  if (post["USER"] == null || post["PASS"] == null)
215  return reply;
216 
217  if (m_UserName != post["USER"].ToString() ||
218  m_Password != post["PASS"].ToString())
219  {
220  return reply;
221  }
222 
223  ConsoleConnection c = new ConsoleConnection();
224  c.last = System.Environment.TickCount;
225  c.lastLineSeen = 0;
226 
227  UUID sessionID = UUID.Random();
228 
229  lock (m_Connections)
230  {
231  m_Connections[sessionID] = c;
232  }
233 
234  string uri = "/ReadResponses/" + sessionID.ToString() + "/";
235 
236  m_Server.AddPollServiceHTTPHandler(
237  uri, new PollServiceEventArgs(null, uri, HasEvents, GetEvents, NoEvents, sessionID,25000)); // 25 secs timeout
238 
239  XmlDocument xmldoc = new XmlDocument();
240  XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
241  "", "");
242 
243  xmldoc.AppendChild(xmlnode);
244  XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
245  "");
246 
247  xmldoc.AppendChild(rootElement);
248 
249  XmlElement id = xmldoc.CreateElement("", "SessionID", "");
250  id.AppendChild(xmldoc.CreateTextNode(sessionID.ToString()));
251 
252  rootElement.AppendChild(id);
253 
254  XmlElement prompt = xmldoc.CreateElement("", "Prompt", "");
255  prompt.AppendChild(xmldoc.CreateTextNode(DefaultPrompt));
256 
257  rootElement.AppendChild(prompt);
258 
259  rootElement.AppendChild(MainConsole.Instance.Commands.GetXml(xmldoc));
260 
261  reply["str_response_string"] = xmldoc.InnerXml;
262  reply["int_response_code"] = 200;
263  reply["content_type"] = "text/xml";
264  reply = CheckOrigin(reply);
265 
266  return reply;
267  }
268 
269  private Hashtable HandleHttpCloseSession(Hashtable request)
270  {
271  DoExpire();
272 
273  Hashtable post = DecodePostString(request["body"].ToString());
274  Hashtable reply = new Hashtable();
275 
276  reply["str_response_string"] = "";
277  reply["int_response_code"] = 404;
278  reply["content_type"] = "text/plain";
279 
280  if (post["ID"] == null)
281  return reply;
282 
283  UUID id;
284  if (!UUID.TryParse(post["ID"].ToString(), out id))
285  return reply;
286 
287  lock (m_Connections)
288  {
289  if (m_Connections.ContainsKey(id))
290  {
291  m_Connections.Remove(id);
292  CloseConnection(id);
293  }
294  }
295 
296  XmlDocument xmldoc = new XmlDocument();
297  XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
298  "", "");
299 
300  xmldoc.AppendChild(xmlnode);
301  XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
302  "");
303 
304  xmldoc.AppendChild(rootElement);
305 
306  XmlElement res = xmldoc.CreateElement("", "Result", "");
307  res.AppendChild(xmldoc.CreateTextNode("OK"));
308 
309  rootElement.AppendChild(res);
310 
311  reply["str_response_string"] = xmldoc.InnerXml;
312  reply["int_response_code"] = 200;
313  reply["content_type"] = "text/xml";
314  reply = CheckOrigin(reply);
315 
316  return reply;
317  }
318 
319  private Hashtable HandleHttpSessionCommand(Hashtable request)
320  {
321  DoExpire();
322 
323  Hashtable post = DecodePostString(request["body"].ToString());
324  Hashtable reply = new Hashtable();
325 
326  reply["str_response_string"] = "";
327  reply["int_response_code"] = 404;
328  reply["content_type"] = "text/plain";
329 
330  if (post["ID"] == null)
331  return reply;
332 
333  UUID id;
334  if (!UUID.TryParse(post["ID"].ToString(), out id))
335  return reply;
336 
337  lock (m_Connections)
338  {
339  if (!m_Connections.ContainsKey(id))
340  return reply;
341  }
342 
343  if (post["COMMAND"] == null)
344  return reply;
345 
346  lock (m_InputData)
347  {
348  m_DataEvent.Set();
349  m_InputData.Add(post["COMMAND"].ToString());
350  }
351 
352  XmlDocument xmldoc = new XmlDocument();
353  XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
354  "", "");
355 
356  xmldoc.AppendChild(xmlnode);
357  XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
358  "");
359 
360  xmldoc.AppendChild(rootElement);
361 
362  XmlElement res = xmldoc.CreateElement("", "Result", "");
363  res.AppendChild(xmldoc.CreateTextNode("OK"));
364 
365  rootElement.AppendChild(res);
366 
367  reply["str_response_string"] = xmldoc.InnerXml;
368  reply["int_response_code"] = 200;
369  reply["content_type"] = "text/xml";
370  reply = CheckOrigin(reply);
371 
372  return reply;
373  }
374 
375  private Hashtable DecodePostString(string data)
376  {
377  Hashtable result = new Hashtable();
378 
379  string[] terms = data.Split(new char[] {'&'});
380 
381  foreach (string term in terms)
382  {
383  string[] elems = term.Split(new char[] {'='});
384  if (elems.Length == 0)
385  continue;
386 
387  string name = System.Web.HttpUtility.UrlDecode(elems[0]);
388  string value = String.Empty;
389 
390  if (elems.Length > 1)
391  value = System.Web.HttpUtility.UrlDecode(elems[1]);
392 
393  result[name] = value;
394  }
395 
396  return result;
397  }
398 
399  public void CloseConnection(UUID id)
400  {
401  try
402  {
403  string uri = "/ReadResponses/" + id.ToString() + "/";
404 
405  m_Server.RemovePollServiceHTTPHandler("", uri);
406  }
407  catch (Exception)
408  {
409  }
410  }
411 
412  private bool HasEvents(UUID RequestID, UUID sessionID)
413  {
414  ConsoleConnection c = null;
415 
416  lock (m_Connections)
417  {
418  if (!m_Connections.ContainsKey(sessionID))
419  return false;
420  c = m_Connections[sessionID];
421  }
422  c.last = System.Environment.TickCount;
423  if (c.lastLineSeen < m_LineNumber)
424  return true;
425  return false;
426  }
427 
428  private Hashtable GetEvents(UUID RequestID, UUID sessionID)
429  {
430  ConsoleConnection c = null;
431 
432  lock (m_Connections)
433  {
434  if (!m_Connections.ContainsKey(sessionID))
435  return NoEvents(RequestID, UUID.Zero);
436  c = m_Connections[sessionID];
437  }
438  c.last = System.Environment.TickCount;
439  if (c.lastLineSeen >= m_LineNumber)
440  return NoEvents(RequestID, UUID.Zero);
441 
442  Hashtable result = new Hashtable();
443 
444  XmlDocument xmldoc = new XmlDocument();
445  XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
446  "", "");
447 
448  xmldoc.AppendChild(xmlnode);
449  XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
450  "");
451 
452  if (c.newConnection)
453  {
454  c.newConnection = false;
455  Output("+++" + DefaultPrompt);
456  }
457 
458  lock (m_Scrollback)
459  {
460  long startLine = m_LineNumber - m_Scrollback.Count;
461  long sendStart = startLine;
462  if (sendStart < c.lastLineSeen)
463  sendStart = c.lastLineSeen;
464 
465  for (long i = sendStart ; i < m_LineNumber ; i++)
466  {
467  XmlElement res = xmldoc.CreateElement("", "Line", "");
468  long line = i + 1;
469  res.SetAttribute("Number", line.ToString());
470  res.AppendChild(xmldoc.CreateTextNode(m_Scrollback[(int)(i - startLine)]));
471 
472  rootElement.AppendChild(res);
473  }
474  }
475  c.lastLineSeen = m_LineNumber;
476 
477  xmldoc.AppendChild(rootElement);
478 
479  result["str_response_string"] = xmldoc.InnerXml;
480  result["int_response_code"] = 200;
481  result["content_type"] = "application/xml";
482  result["keepalive"] = false;
483  result["reusecontext"] = false;
484  result = CheckOrigin(result);
485 
486  return result;
487  }
488 
489  private Hashtable NoEvents(UUID RequestID, UUID id)
490  {
491  Hashtable result = new Hashtable();
492 
493  XmlDocument xmldoc = new XmlDocument();
494  XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
495  "", "");
496 
497  xmldoc.AppendChild(xmlnode);
498  XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
499  "");
500 
501  xmldoc.AppendChild(rootElement);
502 
503  result["str_response_string"] = xmldoc.InnerXml;
504  result["int_response_code"] = 200;
505  result["content_type"] = "text/xml";
506  result["keepalive"] = false;
507  result["reusecontext"] = false;
508  result = CheckOrigin(result);
509 
510  return result;
511  }
512  }
513 }
void ReadConfig(IConfigSource config)
void SetServer(IHttpServer server)
Interface to OpenSimulator's built in HTTP server. Use this to register handlers (http, llsd, xmlrpc, etc.) for given URLs.
Definition: IHttpServer.cs:36
override void Output(string text)
override string ReadLine(string p, bool isCommand, bool e)
override void Output(string text, string level)
A console that processes commands internally