aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/Console/RemoteConsole.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Framework/Console/RemoteConsole.cs')
-rw-r--r--OpenSim/Framework/Console/RemoteConsole.cs317
1 files changed, 263 insertions, 54 deletions
diff --git a/OpenSim/Framework/Console/RemoteConsole.cs b/OpenSim/Framework/Console/RemoteConsole.cs
index 8ad7b0d..f59c902 100644
--- a/OpenSim/Framework/Console/RemoteConsole.cs
+++ b/OpenSim/Framework/Console/RemoteConsole.cs
@@ -34,6 +34,7 @@ using System.Reflection;
34using System.Text; 34using System.Text;
35using System.Text.RegularExpressions; 35using System.Text.RegularExpressions;
36using System.Threading; 36using System.Threading;
37using System.Timers;
37using OpenMetaverse; 38using OpenMetaverse;
38using Nini.Config; 39using Nini.Config;
39using OpenSim.Framework.Servers.HttpServer; 40using OpenSim.Framework.Servers.HttpServer;
@@ -41,51 +42,147 @@ using log4net;
41 42
42namespace OpenSim.Framework.Console 43namespace OpenSim.Framework.Console
43{ 44{
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 45 // A console that uses REST interfaces
52 // 46 //
53 public class RemoteConsole : CommandConsole 47 public class RemoteConsole : CommandConsole
54 { 48 {
55 private IHttpServer m_Server = null; 49 // Connection specific data, indexed by a session ID
56 private IConfigSource m_Config = null; 50 // we create when a client connects.
57 51 protected class ConsoleConnection
58 private List<string> m_Scrollback = new List<string>(); 52 {
59 private ManualResetEvent m_DataEvent = new ManualResetEvent(false); 53 // Last activity from the client
60 private List<string> m_InputData = new List<string>(); 54 public int last;
61 private long m_LineNumber = 0; 55
62 private Dictionary<UUID, ConsoleConnection> m_Connections = 56 // Last line of scrollback posted to this client
57 public long lastLineSeen;
58
59 // True if this is a new connection, e.g. has never
60 // displayed a prompt to the user.
61 public bool newConnection = true;
62 }
63
64 // A line in the scrollback buffer.
65 protected class ScrollbackEntry
66 {
67 // The line number of this entry
68 public long lineNumber;
69
70 // The text to send to the client
71 public string text;
72
73 // The level this should be logged as. Omitted for
74 // prompts and input echo.
75 public string level;
76
77 // True if the text above is a prompt, e.g. the
78 // client should turn on the cursor / accept input
79 public bool isPrompt;
80
81 // True if the requested input is a command. A
82 // client may offer help or validate input if
83 // this is set. If false, input should be sent
84 // as typed.
85 public bool isCommand;
86
87 // True if this text represents a line of text that
88 // was input in response to a prompt. A client should
89 // turn off the cursor and refrain from sending commands
90 // until a new prompt is received.
91 public bool isInput;
92 }
93
94 // Data that is relevant to all connections
95
96 // The scrollback buffer
97 protected List<ScrollbackEntry> m_Scrollback = new List<ScrollbackEntry>();
98
99 // Monotonously incrementing line number. This may eventually
100 // wrap. No provision is made for that case because 64 bits
101 // is a long, long time.
102 protected long m_lineNumber = 0;
103
104 // These two variables allow us to send the correct
105 // information about the prompt status to the client,
106 // irrespective of what may have run off the top of the
107 // scrollback buffer;
108 protected bool m_expectingInput = false;
109 protected bool m_expectingCommand = true;
110 protected string m_lastPromptUsed;
111
112 // This is the list of things received from clients.
113 // Note: Race conditions can happen. If a client sends
114 // something while nothing is expected, it will be
115 // intepreted as input to the next prompt. For
116 // commands this is largely correct. For other prompts,
117 // YMMV.
118 // TODO: Find a better way to fix this
119 protected List<string> m_InputData = new List<string>();
120
121 // Event to allow ReadLine to wait synchronously even though
122 // everthing else is asynchronous here.
123 protected ManualResetEvent m_DataEvent = new ManualResetEvent(false);
124
125 // The list of sessions we maintain. Unlike other console types,
126 // multiple users on the same console are explicitly allowed.
127 protected Dictionary<UUID, ConsoleConnection> m_Connections =
63 new Dictionary<UUID, ConsoleConnection>(); 128 new Dictionary<UUID, ConsoleConnection>();
64 private string m_UserName = String.Empty; 129
65 private string m_Password = String.Empty; 130 // Timer to control expiration of sessions that have been
66 private string m_AllowedOrigin = String.Empty; 131 // disconnected.
132 protected System.Timers.Timer m_expireTimer = new System.Timers.Timer(5000);
133
134 // The less interesting stuff that makes the actual server
135 // work.
136 protected IHttpServer m_Server = null;
137 protected IConfigSource m_Config = null;
138
139 protected string m_UserName = String.Empty;
140 protected string m_Password = String.Empty;
141 protected string m_AllowedOrigin = String.Empty;
142
67 143
68 public RemoteConsole(string defaultPrompt) : base(defaultPrompt) 144 public RemoteConsole(string defaultPrompt) : base(defaultPrompt)
69 { 145 {
146 // There is something wrong with this architecture.
147 // A prompt is sent on every single input, so why have this?
148 // TODO: Investigate and fix.
149 m_lastPromptUsed = defaultPrompt;
150
151 // Start expiration of sesssions.
152 m_expireTimer.Elapsed += DoExpire;
153 m_expireTimer.Start();
70 } 154 }
71 155
72 public void ReadConfig(IConfigSource config) 156 public void ReadConfig(IConfigSource config)
73 { 157 {
74 m_Config = config; 158 m_Config = config;
75 159
160 // We're pulling this from the 'Network' section for legacy
161 // compatibility. However, this is so essentially insecure
162 // that TLS and client certs should be used instead of
163 // a username / password.
76 IConfig netConfig = m_Config.Configs["Network"]; 164 IConfig netConfig = m_Config.Configs["Network"];
165
77 if (netConfig == null) 166 if (netConfig == null)
78 return; 167 return;
79 168
169 // Get the username and password.
80 m_UserName = netConfig.GetString("ConsoleUser", String.Empty); 170 m_UserName = netConfig.GetString("ConsoleUser", String.Empty);
81 m_Password = netConfig.GetString("ConsolePass", String.Empty); 171 m_Password = netConfig.GetString("ConsolePass", String.Empty);
172
173 // Woefully underdocumented, this is what makes javascript
174 // console clients work. Set to "*" for anywhere or (better)
175 // to specific addresses.
82 m_AllowedOrigin = netConfig.GetString("ConsoleAllowedOrigin", String.Empty); 176 m_AllowedOrigin = netConfig.GetString("ConsoleAllowedOrigin", String.Empty);
83 } 177 }
84 178
85 public void SetServer(IHttpServer server) 179 public void SetServer(IHttpServer server)
86 { 180 {
181 // This is called by the framework to give us the server
182 // instance (means: port) to work with.
87 m_Server = server; 183 m_Server = server;
88 184
185 // Add our handlers
89 m_Server.AddHTTPHandler("/StartSession/", HandleHttpStartSession); 186 m_Server.AddHTTPHandler("/StartSession/", HandleHttpStartSession);
90 m_Server.AddHTTPHandler("/CloseSession/", HandleHttpCloseSession); 187 m_Server.AddHTTPHandler("/CloseSession/", HandleHttpCloseSession);
91 m_Server.AddHTTPHandler("/SessionCommand/", HandleHttpSessionCommand); 188 m_Server.AddHTTPHandler("/SessionCommand/", HandleHttpSessionCommand);
@@ -93,38 +190,84 @@ namespace OpenSim.Framework.Console
93 190
94 public override void Output(string text, string level) 191 public override void Output(string text, string level)
95 { 192 {
193 Output(text, level, false, false, false);
194 }
195
196 protected void Output(string text, string level, bool isPrompt, bool isCommand, bool isInput)
197 {
198 // Increment the line number. It was 0 and they start at 1
199 // so we need to pre-increment.
200 m_lineNumber++;
201
202 // Create and populate the new entry.
203 ScrollbackEntry newEntry = new ScrollbackEntry();
204
205 newEntry.lineNumber = m_lineNumber;
206 newEntry.text = text;
207 newEntry.level = level;
208 newEntry.isPrompt = isPrompt;
209 newEntry.isCommand = isCommand;
210 newEntry.isInput = isInput;
211
212 // Add a line to the scrollback. In some cases, that may not
213 // actually be a line of text.
96 lock (m_Scrollback) 214 lock (m_Scrollback)
97 { 215 {
216 // Prune the scrollback to the length se send as connect
217 // burst to give the user some context.
98 while (m_Scrollback.Count >= 1000) 218 while (m_Scrollback.Count >= 1000)
99 m_Scrollback.RemoveAt(0); 219 m_Scrollback.RemoveAt(0);
100 m_LineNumber++; 220
101 m_Scrollback.Add(String.Format("{0}", m_LineNumber)+":"+level+":"+text); 221 m_Scrollback.Add(newEntry);
102 } 222 }
223
224 // Let the rest of the system know we have output something.
103 FireOnOutput(text.Trim()); 225 FireOnOutput(text.Trim());
226
227 // Also display it for debugging.
104 System.Console.WriteLine(text.Trim()); 228 System.Console.WriteLine(text.Trim());
105 } 229 }
106 230
107 public override void Output(string text) 231 public override void Output(string text)
108 { 232 {
109 Output(text, "normal"); 233 // Output plain (non-logging style) text.
234 Output(text, String.Empty, false, false, false);
110 } 235 }
111 236
112 public override string ReadLine(string p, bool isCommand, bool e) 237 public override string ReadLine(string p, bool isCommand, bool e)
113 { 238 {
239 // Output the prompt an prepare to wait. This
240 // is called on a dedicated console thread and
241 // needs to be synchronous. Old architecture but
242 // not worth upgrading.
114 if (isCommand) 243 if (isCommand)
115 Output("+++"+p); 244 {
245 m_expectingInput = true;
246 m_expectingCommand = true;
247 Output(p, String.Empty, true, true, false);
248 m_lastPromptUsed = p;
249 }
116 else 250 else
117 Output("-++"+p); 251 {
252 m_expectingInput = true;
253 Output(p, String.Empty, true, false, false);
254 }
118 255
256
257 // Here is where we wait for the user to input something.
119 m_DataEvent.WaitOne(); 258 m_DataEvent.WaitOne();
120 259
121 string cmdinput; 260 string cmdinput;
122 261
262 // Check for empty input. Read input if not empty.
123 lock (m_InputData) 263 lock (m_InputData)
124 { 264 {
125 if (m_InputData.Count == 0) 265 if (m_InputData.Count == 0)
126 { 266 {
127 m_DataEvent.Reset(); 267 m_DataEvent.Reset();
268 m_expectingInput = false;
269 m_expectingCommand = false;
270
128 return ""; 271 return "";
129 } 272 }
130 273
@@ -135,8 +278,19 @@ namespace OpenSim.Framework.Console
135 278
136 } 279 }
137 280
281 m_expectingInput = false;
282 m_expectingCommand = false;
283
284 // Echo to all the other users what we have done. This
285 // will also go to ourselves.
286 Output (cmdinput, String.Empty, false, false, true);
287
288 // If this is a command, we need to resolve and execute it.
138 if (isCommand) 289 if (isCommand)
139 { 290 {
291 // This call will actually execute the command and create
292 // any output associated with it. The core just gets an
293 // empty string so it will call again immediately.
140 string[] cmd = Commands.Resolve(Parser.Parse(cmdinput)); 294 string[] cmd = Commands.Resolve(Parser.Parse(cmdinput));
141 295
142 if (cmd.Length != 0) 296 if (cmd.Length != 0)
@@ -151,18 +305,23 @@ namespace OpenSim.Framework.Console
151 return String.Empty; 305 return String.Empty;
152 } 306 }
153 } 307 }
308
309 // Return the raw input string if not a command.
154 return cmdinput; 310 return cmdinput;
155 } 311 }
156 312
157 private Hashtable CheckOrigin(Hashtable result) 313 // Very simplistic static access control header.
314 protected Hashtable CheckOrigin(Hashtable result)
158 { 315 {
159 if (!string.IsNullOrEmpty(m_AllowedOrigin)) 316 if (!string.IsNullOrEmpty(m_AllowedOrigin))
160 result["access_control_allow_origin"] = m_AllowedOrigin; 317 result["access_control_allow_origin"] = m_AllowedOrigin;
318
161 return result; 319 return result;
162 } 320 }
321
163 /* TODO: Figure out how PollServiceHTTPHandler can access the request headers 322 /* TODO: Figure out how PollServiceHTTPHandler can access the request headers
164 * in order to use m_AllowedOrigin as a regular expression 323 * in order to use m_AllowedOrigin as a regular expression
165 private Hashtable CheckOrigin(Hashtable headers, Hashtable result) 324 protected Hashtable CheckOrigin(Hashtable headers, Hashtable result)
166 { 325 {
167 if (!string.IsNullOrEmpty(m_AllowedOrigin)) 326 if (!string.IsNullOrEmpty(m_AllowedOrigin))
168 { 327 {
@@ -177,18 +336,23 @@ namespace OpenSim.Framework.Console
177 } 336 }
178 */ 337 */
179 338
180 private void DoExpire() 339 protected void DoExpire(Object sender, ElapsedEventArgs e)
181 { 340 {
341 // Iterate the list of console connections and find those we
342 // haven't heard from for longer then the longpoll interval.
343 // Remove them.
182 List<UUID> expired = new List<UUID>(); 344 List<UUID> expired = new List<UUID>();
183 345
184 lock (m_Connections) 346 lock (m_Connections)
185 { 347 {
348 // Mark the expired ones
186 foreach (KeyValuePair<UUID, ConsoleConnection> kvp in m_Connections) 349 foreach (KeyValuePair<UUID, ConsoleConnection> kvp in m_Connections)
187 { 350 {
188 if (System.Environment.TickCount - kvp.Value.last > 500000) 351 if (System.Environment.TickCount - kvp.Value.last > 500000)
189 expired.Add(kvp.Key); 352 expired.Add(kvp.Key);
190 } 353 }
191 354
355 // Delete them
192 foreach (UUID id in expired) 356 foreach (UUID id in expired)
193 { 357 {
194 m_Connections.Remove(id); 358 m_Connections.Remove(id);
@@ -197,10 +361,10 @@ namespace OpenSim.Framework.Console
197 } 361 }
198 } 362 }
199 363
200 private Hashtable HandleHttpStartSession(Hashtable request) 364 // Start a new session.
365 protected Hashtable HandleHttpStartSession(Hashtable request)
201 { 366 {
202 DoExpire(); 367 // The login is in the form of a http form post
203
204 Hashtable post = DecodePostString(request["body"].ToString()); 368 Hashtable post = DecodePostString(request["body"].ToString());
205 Hashtable reply = new Hashtable(); 369 Hashtable reply = new Hashtable();
206 370
@@ -208,6 +372,7 @@ namespace OpenSim.Framework.Console
208 reply["int_response_code"] = 401; 372 reply["int_response_code"] = 401;
209 reply["content_type"] = "text/plain"; 373 reply["content_type"] = "text/plain";
210 374
375 // Check user name and password
211 if (m_UserName == String.Empty) 376 if (m_UserName == String.Empty)
212 return reply; 377 return reply;
213 378
@@ -220,22 +385,28 @@ namespace OpenSim.Framework.Console
220 return reply; 385 return reply;
221 } 386 }
222 387
388 // Set up the new console connection record
223 ConsoleConnection c = new ConsoleConnection(); 389 ConsoleConnection c = new ConsoleConnection();
224 c.last = System.Environment.TickCount; 390 c.last = System.Environment.TickCount;
225 c.lastLineSeen = 0; 391 c.lastLineSeen = 0;
226 392
393 // Assign session ID
227 UUID sessionID = UUID.Random(); 394 UUID sessionID = UUID.Random();
228 395
396 // Add connection to list.
229 lock (m_Connections) 397 lock (m_Connections)
230 { 398 {
231 m_Connections[sessionID] = c; 399 m_Connections[sessionID] = c;
232 } 400 }
233 401
402 // This call is a CAP. The URL is the authentication.
234 string uri = "/ReadResponses/" + sessionID.ToString() + "/"; 403 string uri = "/ReadResponses/" + sessionID.ToString() + "/";
235 404
236 m_Server.AddPollServiceHTTPHandler( 405 m_Server.AddPollServiceHTTPHandler(
237 uri, new PollServiceEventArgs(null, uri, HasEvents, GetEvents, NoEvents, sessionID,25000)); // 25 secs timeout 406 uri, new PollServiceEventArgs(null, uri, HasEvents, GetEvents, NoEvents, sessionID,25000)); // 25 secs timeout
238 407
408 // Our reply is an XML document.
409 // TODO: Change this to Linq.Xml
239 XmlDocument xmldoc = new XmlDocument(); 410 XmlDocument xmldoc = new XmlDocument();
240 XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration, 411 XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
241 "", ""); 412 "", "");
@@ -252,12 +423,13 @@ namespace OpenSim.Framework.Console
252 rootElement.AppendChild(id); 423 rootElement.AppendChild(id);
253 424
254 XmlElement prompt = xmldoc.CreateElement("", "Prompt", ""); 425 XmlElement prompt = xmldoc.CreateElement("", "Prompt", "");
255 prompt.AppendChild(xmldoc.CreateTextNode(DefaultPrompt)); 426 prompt.AppendChild(xmldoc.CreateTextNode(m_lastPromptUsed));
256 427
257 rootElement.AppendChild(prompt); 428 rootElement.AppendChild(prompt);
258 429
259 rootElement.AppendChild(MainConsole.Instance.Commands.GetXml(xmldoc)); 430 rootElement.AppendChild(MainConsole.Instance.Commands.GetXml(xmldoc));
260 431
432 // Set up the response and check origin
261 reply["str_response_string"] = xmldoc.InnerXml; 433 reply["str_response_string"] = xmldoc.InnerXml;
262 reply["int_response_code"] = 200; 434 reply["int_response_code"] = 200;
263 reply["content_type"] = "text/xml"; 435 reply["content_type"] = "text/xml";
@@ -266,10 +438,9 @@ namespace OpenSim.Framework.Console
266 return reply; 438 return reply;
267 } 439 }
268 440
269 private Hashtable HandleHttpCloseSession(Hashtable request) 441 // Client closes session. Clean up.
442 protected Hashtable HandleHttpCloseSession(Hashtable request)
270 { 443 {
271 DoExpire();
272
273 Hashtable post = DecodePostString(request["body"].ToString()); 444 Hashtable post = DecodePostString(request["body"].ToString());
274 Hashtable reply = new Hashtable(); 445 Hashtable reply = new Hashtable();
275 446
@@ -316,10 +487,9 @@ namespace OpenSim.Framework.Console
316 return reply; 487 return reply;
317 } 488 }
318 489
319 private Hashtable HandleHttpSessionCommand(Hashtable request) 490 // Command received from the client.
491 protected Hashtable HandleHttpSessionCommand(Hashtable request)
320 { 492 {
321 DoExpire();
322
323 Hashtable post = DecodePostString(request["body"].ToString()); 493 Hashtable post = DecodePostString(request["body"].ToString());
324 Hashtable reply = new Hashtable(); 494 Hashtable reply = new Hashtable();
325 495
@@ -327,6 +497,7 @@ namespace OpenSim.Framework.Console
327 reply["int_response_code"] = 404; 497 reply["int_response_code"] = 404;
328 reply["content_type"] = "text/plain"; 498 reply["content_type"] = "text/plain";
329 499
500 // Check the ID
330 if (post["ID"] == null) 501 if (post["ID"] == null)
331 return reply; 502 return reply;
332 503
@@ -334,21 +505,25 @@ namespace OpenSim.Framework.Console
334 if (!UUID.TryParse(post["ID"].ToString(), out id)) 505 if (!UUID.TryParse(post["ID"].ToString(), out id))
335 return reply; 506 return reply;
336 507
508 // Find the connection for that ID.
337 lock (m_Connections) 509 lock (m_Connections)
338 { 510 {
339 if (!m_Connections.ContainsKey(id)) 511 if (!m_Connections.ContainsKey(id))
340 return reply; 512 return reply;
341 } 513 }
342 514
515 // Empty post. Just error out.
343 if (post["COMMAND"] == null) 516 if (post["COMMAND"] == null)
344 return reply; 517 return reply;
345 518
519 // Place the input data in the buffer.
346 lock (m_InputData) 520 lock (m_InputData)
347 { 521 {
348 m_DataEvent.Set(); 522 m_DataEvent.Set();
349 m_InputData.Add(post["COMMAND"].ToString()); 523 m_InputData.Add(post["COMMAND"].ToString());
350 } 524 }
351 525
526 // Create the XML reply document.
352 XmlDocument xmldoc = new XmlDocument(); 527 XmlDocument xmldoc = new XmlDocument();
353 XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration, 528 XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
354 "", ""); 529 "", "");
@@ -372,7 +547,8 @@ namespace OpenSim.Framework.Console
372 return reply; 547 return reply;
373 } 548 }
374 549
375 private Hashtable DecodePostString(string data) 550 // Decode a HTTP form post to a Hashtable
551 protected Hashtable DecodePostString(string data)
376 { 552 {
377 Hashtable result = new Hashtable(); 553 Hashtable result = new Hashtable();
378 554
@@ -389,13 +565,14 @@ namespace OpenSim.Framework.Console
389 565
390 if (elems.Length > 1) 566 if (elems.Length > 1)
391 value = System.Web.HttpUtility.UrlDecode(elems[1]); 567 value = System.Web.HttpUtility.UrlDecode(elems[1]);
392 568
393 result[name] = value; 569 result[name] = value;
394 } 570 }
395 571
396 return result; 572 return result;
397 } 573 }
398 574
575 // Close the CAP receiver for the responses for a given client.
399 public void CloseConnection(UUID id) 576 public void CloseConnection(UUID id)
400 { 577 {
401 try 578 try
@@ -409,7 +586,9 @@ namespace OpenSim.Framework.Console
409 } 586 }
410 } 587 }
411 588
412 private bool HasEvents(UUID RequestID, UUID sessionID) 589 // Check if there is anything to send. Return true if this client has
590 // lines pending.
591 protected bool HasEvents(UUID RequestID, UUID sessionID)
413 { 592 {
414 ConsoleConnection c = null; 593 ConsoleConnection c = null;
415 594
@@ -420,13 +599,15 @@ namespace OpenSim.Framework.Console
420 c = m_Connections[sessionID]; 599 c = m_Connections[sessionID];
421 } 600 }
422 c.last = System.Environment.TickCount; 601 c.last = System.Environment.TickCount;
423 if (c.lastLineSeen < m_LineNumber) 602 if (c.lastLineSeen < m_lineNumber)
424 return true; 603 return true;
425 return false; 604 return false;
426 } 605 }
427 606
428 private Hashtable GetEvents(UUID RequestID, UUID sessionID) 607 // Send all pending output to the client.
608 protected Hashtable GetEvents(UUID RequestID, UUID sessionID)
429 { 609 {
610 // Find the connection that goes with this client.
430 ConsoleConnection c = null; 611 ConsoleConnection c = null;
431 612
432 lock (m_Connections) 613 lock (m_Connections)
@@ -435,12 +616,15 @@ namespace OpenSim.Framework.Console
435 return NoEvents(RequestID, UUID.Zero); 616 return NoEvents(RequestID, UUID.Zero);
436 c = m_Connections[sessionID]; 617 c = m_Connections[sessionID];
437 } 618 }
619
620 // If we have nothing to send, send the no events response.
438 c.last = System.Environment.TickCount; 621 c.last = System.Environment.TickCount;
439 if (c.lastLineSeen >= m_LineNumber) 622 if (c.lastLineSeen >= m_lineNumber)
440 return NoEvents(RequestID, UUID.Zero); 623 return NoEvents(RequestID, UUID.Zero);
441 624
442 Hashtable result = new Hashtable(); 625 Hashtable result = new Hashtable();
443 626
627 // Create the response document.
444 XmlDocument xmldoc = new XmlDocument(); 628 XmlDocument xmldoc = new XmlDocument();
445 XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration, 629 XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
446 "", ""); 630 "", "");
@@ -449,30 +633,53 @@ namespace OpenSim.Framework.Console
449 XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession", 633 XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
450 ""); 634 "");
451 635
452 if (c.newConnection) 636 //if (c.newConnection)
453 { 637 //{
454 c.newConnection = false; 638 // c.newConnection = false;
455 Output("+++" + DefaultPrompt); 639 // Output("+++" + DefaultPrompt);
456 } 640 //}
457 641
458 lock (m_Scrollback) 642 lock (m_Scrollback)
459 { 643 {
460 long startLine = m_LineNumber - m_Scrollback.Count; 644 long startLine = m_lineNumber - m_Scrollback.Count;
461 long sendStart = startLine; 645 long sendStart = startLine;
462 if (sendStart < c.lastLineSeen) 646 if (sendStart < c.lastLineSeen)
463 sendStart = c.lastLineSeen; 647 sendStart = c.lastLineSeen;
464 648
465 for (long i = sendStart ; i < m_LineNumber ; i++) 649 for (long i = sendStart ; i < m_lineNumber ; i++)
466 { 650 {
651 ScrollbackEntry e = m_Scrollback[(int)(i - startLine)];
652
467 XmlElement res = xmldoc.CreateElement("", "Line", ""); 653 XmlElement res = xmldoc.CreateElement("", "Line", "");
468 long line = i + 1; 654 res.SetAttribute("Number", e.lineNumber.ToString());
469 res.SetAttribute("Number", line.ToString()); 655 res.SetAttribute("Level", e.level);
470 res.AppendChild(xmldoc.CreateTextNode(m_Scrollback[(int)(i - startLine)])); 656 // Don't include these for the scrollback, we'll send the
657 // real state later.
658 if (!c.newConnection)
659 {
660 res.SetAttribute("Prompt", e.isPrompt ? "true" : "false");
661 res.SetAttribute("Command", e.isCommand ? "true" : "false");
662 res.SetAttribute("Input", e.isInput ? "true" : "false");
663 }
664 else if (i == m_lineNumber - 1) // Last line for a new connection
665 {
666 res.SetAttribute("Prompt", m_expectingInput ? "true" : "false");
667 res.SetAttribute("Command", m_expectingCommand ? "true" : "false");
668 res.SetAttribute("Input", (!m_expectingInput) ? "true" : "false");
669 }
670 else
671 {
672 res.SetAttribute("Input", e.isInput ? "true" : "false");
673 }
674
675 res.AppendChild(xmldoc.CreateTextNode(e.text));
471 676
472 rootElement.AppendChild(res); 677 rootElement.AppendChild(res);
473 } 678 }
474 } 679 }
475 c.lastLineSeen = m_LineNumber; 680
681 c.lastLineSeen = m_lineNumber;
682 c.newConnection = false;
476 683
477 xmldoc.AppendChild(rootElement); 684 xmldoc.AppendChild(rootElement);
478 685
@@ -486,7 +693,9 @@ namespace OpenSim.Framework.Console
486 return result; 693 return result;
487 } 694 }
488 695
489 private Hashtable NoEvents(UUID RequestID, UUID id) 696 // This is really just a no-op. It generates what is sent
697 // to the client if the poll times out without any events.
698 protected Hashtable NoEvents(UUID RequestID, UUID id)
490 { 699 {
491 Hashtable result = new Hashtable(); 700 Hashtable result = new Hashtable();
492 701