From 2ba93379b7cae75c37f20b0a257aaee2aeb43805 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 12 Mar 2014 16:16:31 -0400 Subject: [PATCH] Track some particularly key terminal states and replay them for new clients ESXi requires a distinctly different keypad mode and 'shift in' character set. Track requests for those states. Reset on 'null' character, which seems to only be emitted by UEFI so far. Ideally, things change such that we can remove that workaround. --- confluent/consoleserver.py | 54 +++++++++++++++++++++++++++----------- confluent/log.py | 9 +++++-- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/confluent/consoleserver.py b/confluent/consoleserver.py index c0d9bfea..4eb92579 100644 --- a/confluent/consoleserver.py +++ b/confluent/consoleserver.py @@ -26,6 +26,8 @@ class _ConsoleHandler(object): self.buffer = bytearray() self._connect() self.users = {} + self.appmodedetected = False + self.shiftin = None def _connect(self): self._console = plugin.handle_path( @@ -45,13 +47,10 @@ class _ConsoleHandler(object): return hdl def flushbuffer(self): - #TODO:log the old stuff - if len(self.buffer) > 1024: - self.buffer = bytearray(self.buffer[-1024:]) - #Will be interesting to keep track of logged but - #retained data, must only log data not already - #flushed - #also, timestamp data... + # Logging is handled in a different stream + # this buffer is now just for having screen redraw on + # connect + self.buffer = bytearray(self.buffer[-8192:]) def get_console_output(self, data): # Spawn as a greenthread, return control as soon as possible @@ -87,18 +86,35 @@ class _ConsoleHandler(object): if data == conapi.ConsoleEvent.Disconnect: self._connect() return - self.logger.log(data) + prefix = '' + if '\0' in data: # there is a null in the output + # the proper response is to do nothing, but here using it as a cue + # that perhaps firmware has reset since that's the only place + # observed so far. Lose the shiftin and app mode when detected + prefix = '\x1b[?1l' + self.shiftin = None + self.appmodedetected = False + if '\x1b[?1h' in data: # remember the session wants the client to be in + # 'application mode' Thus far only observed on esxi + self.appmodedetected = True + if '\x1b)0' in data: + # console indicates it wants access to special drawing characters + self.shiftin = '0' + eventdata = 0 + if self.appmodedetected: + eventdata = eventdata | 1 + if self.shiftin is not None: + eventdata = eventdata | 2 + self.logger.log(data, eventdata=eventdata) self.buffer += data #TODO: analyze buffer for registered events, examples: # panics # certificate signing request - if len(self.buffer) > 8192: - #call to function to get generic data to log if applicable - #and shrink buffer + if len(self.buffer) > 16384: self.flushbuffer() for rcpt in self.rcpts.itervalues(): try: - rcpt(data) + rcpt(prefix + data) except: pass @@ -110,23 +126,31 @@ class _ConsoleHandler(object): #For now, just try to seek back in buffer to find a clear screen #If that fails, just return buffer #a scheme always tracking the last clear screen would be too costly + retdata = '' + if self.shiftin is not None: #detected that terminal requested a + #shiftin character set, relay that to the terminal that cannected + retdata += '\x1b)' + self.shiftin + if self.appmodedetected: + retdata += '\x1b[?1h' + else: + retdata += '\x1b[?1l' #an alternative would be to emulate a VT100 to know what the #whole screen would look like #this is one scheme to clear screen, move cursor then clear bufidx = self.buffer.rfind('\x1b[H\x1b[J') if bufidx >= 0: - return str(self.buffer[bufidx:]) + return retdata + str(self.buffer[bufidx:]) #another scheme is the 2J scheme bufidx = self.buffer.rfind('\x1b[2J') if bufidx >= 0: # there was some sort of clear screen event # somewhere in the buffer, replay from that point # in hopes that it reproduces the screen - return str(self.buffer[bufidx:]) + return retdata + str(self.buffer[bufidx:]) else: #we have no indication of last erase, play back last kibibyte #to give some sense of context anyway - return str(self.buffer[-1024:]) + return retdata + str(self.buffer[-1024:]) def write(self, data): #TODO.... take note of data coming in from audit/log perspective? diff --git a/confluent/log.py b/confluent/log.py index 1482d356..a2c7a6f9 100644 --- a/confluent/log.py +++ b/confluent/log.py @@ -118,9 +118,12 @@ class Logger(object): '%b %d %H:%M:%S ', time.localtime(tstamp)) offset = self.textfile.tell() + len(textdate) datalen = len(data) + eventaux = entry[4] + if eventaux is None: + eventaux = 0 # metadata length is always 16 for this code at the moment binrecord = struct.pack(">BBIHIBBH", - 16, ltype, offset, datalen, tstamp, evtdata, entry[4], 0) + 16, ltype, offset, datalen, tstamp, evtdata, eventaux, 0) if self.isconsole: if ltype == 2: textrecord = data @@ -136,7 +139,7 @@ class Logger(object): self.closer = eventlet.spawn_after(15, self.closelog) self.writer = None - def log(self, logdata=None, ltype=None, event=0, eventdata=0): + def log(self, logdata=None, ltype=None, event=0, eventdata=None): if type(logdata) not in (str, unicode, dict): raise Exception("Unsupported logdata") if ltype is None: @@ -154,6 +157,8 @@ class Logger(object): event == 0 and self.logentries[-1][0] == 2 and self.logentries[-1][1] == timestamp): self.logentries[-1][2] += logdata + if eventdata is not None: + self.logentries[-1][4] = eventdata else: self.logentries.append([ltype, timestamp, logdata, event, eventdata]) if self.writer is None: