diff options
Diffstat (limited to '')
-rw-r--r-- | nselib/tn3270.lua | 1402 |
1 files changed, 1402 insertions, 0 deletions
diff --git a/nselib/tn3270.lua b/nselib/tn3270.lua new file mode 100644 index 0000000..d49f4da --- /dev/null +++ b/nselib/tn3270.lua @@ -0,0 +1,1402 @@ +--- +-- TN3270 Emulator Library +-- +-- Summary +-- * This library implements an RFC 1576 and 2355 (somewhat) compliant TN3270 emulator. +-- +-- The library consists of one class <code>Telnet</code> consisting of multiple +-- functions required for initiating a TN3270 connection. +-- +-- The following sample code illustrates how scripts can use this class +-- to interface with a mainframe: +-- +-- <code> +-- mainframe = Telnet:new() +-- status, err = mainframe:initiate(host, port) +-- status, err = mainframe:send_cursor("LOGON APPLID(TSO)") +-- mainframe:get_data() +-- curr_screen = mainframe:get_screen() +-- status, err = mainframe:disconnect() +-- </code> +-- +-- The implementation is based on packet dumps, x3270, the excellent decoding +-- provided by Wireshark and the Data Stream Programmers Reference (Dec 88) + +local stdnse = require "stdnse" +local drda = require "drda" -- We only need this to decode EBCDIC +local comm = require "comm" +local math = require "math" +local nmap = require "nmap" +local string = require "string" +local table = require "table" + +_ENV = stdnse.module("tn3270", stdnse.seeall) + +Telnet = { + --__index = Telnet, + + commands = { + SE = "\240", -- End of subnegotiation parameters + SB = "\250", -- Sub-option to follow + WILL = "\251", -- Will; request or confirm option begin + WONT = "\252", -- Wont; deny option request + DO = "\253", -- Do = Request or confirm remote option + DONT = "\254", -- Don't = Demand or confirm option halt + IAC = "\255", -- Interpret as Command + SEND = "\001", -- Sub-process negotiation SEND command + IS = "\000", -- Sub-process negotiation IS command + EOR = "\239" + }, + tncommands = { + ASSOCIATE = "\000", + CONNECT = "\001", + DEVICETYPE = "\002", + FUNCTIONS = "\003", + IS = "\004", + REASON = "\005", + REJECT = "\006", + REQUEST = "\007", + RESPONSES = "\002", + SEND = "\008", + EOR = "\239" + }, + + -- Thesse are the options we accept for telnet + options = { + BINARY = "\000", + EOR = "\025", + TTYPE = "\024", + TN3270 = "\028", + TN3270E = "\040" + }, + + command = { + EAU = "\015", + EW = "\005", + EWA = "\013", + RB = "\002", + RM = "\006", + RMA = "", + W = "\001", + WSF = "\017", + NOP = "\003", + SNS = "\004", + SNSID = "\228" + }, + sna_command ={ + RMA = "\110", + EAU = "\111", + EWA = "\126", + W = "\241", + RB = "\242", + WSF = "\243", + EW = "\245", + NOP = "\003", + RM = "\246" + }, + + orders = { + SF = "\029", + SFE = "\041", + SBA = "\017", + SA = "\040", + MF = "\044", + IC = "\019", + PT = "\005", + RA = "\060", + EUA = "\018", + GE = "\008" + }, + + fcorders = { + NUL = "\000", + SUB = "\063", + DUP = "\028", + FM = "\030", + FF = "\012", + CR = "\013", + NL = "\021", + EM = "\025", + EO = "\255" + }, + + aids = { + NO = 0x60, -- no aid + QREPLY = 0x61, -- reply + ENTER = 0x7d, -- enter + PF1 = 0xf1, + PF2 = 0xf2, + PF3 = 0xf3, + PF4 = 0xf4, + PF5 = 0xf5, + PF6 = 0xf6, + PF7 = 0xf7, + PF8 = 0xf8, + PF9 = 0xf9, + PF10 = 0x7a, + PF11 = 0x7b, + PF12 = 0x7c, + PF13 = 0xc1, + PF14 = 0xc2, + PF15 = 0xc3, + PF16 = 0xc4, + PF17 = 0xc5, + PF18 = 0xc6, + PF19 = 0xc7, + PF20 = 0xc8, + PF21 = 0xc9, + PF22 = 0x4a, + PF23 = 0x4b, + PF24 = 0x4c, + OICR = 0xe6, + MSR_MHS = 0xe7, + SELECT = 0x7e, + PA1 = 0x6c, + PA2 = 0x6e, + PA3 = 0x6b, + CLEAR = 0x6d, + SYSREQ = 0xf0 + }, + + -- used to translate buffer addresses + code_table = { + 0x40, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, + 0xC8, 0xC9, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, + 0xD8, 0xD9, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F + }, + + -- Variables used for Telnet Negotiation and data buffers + word_state = { "Negotiating", "Connected", "TN3270 mode", "TN3270E mode"}, + NEGOTIATING = 1, + CONNECTED = 2, + TN3270_DATA = 3, + TN3270E_DATA = 4, + device_type = "IBM-3278-2", + + -- TN3270E Header variables + tn3270_header = { + data_type = '', + request_flag = '', + response_flag = '', + seq_number = '' + }, + + -- TN3270 Datatream Processing flags + NO_OUTPUT = 0, + OUTPUT = 1, + BAD_COMMAND = 2, + BAD_ADDRESS = 3, + NO_AID = 0x60, + aid = 0x60, -- initial Attention Identifier is No AID + + -- Header response flags. + NO_RESPONSE = 0x00, + ERROR_RESPONSE = 0x01, + ALWAYS_RESPONSE = 0x02, + POSITIVE_RESPONSE = 0x00, + NEGATIVE_RESPONSE = 0x01, + + -- Header data type names. + DT_3270_DATA = 0x00, + DT_SCS_DATA = 0x01, + DT_RESPONSE = 0x02, + DT_BIND_IMAGE = 0x03, + DT_UNBIND = 0x04, + DT_NVT_DATA = 0x05, + DT_REQUEST = 0x06, + DT_SSCP_LU_DATA = 0x07, + DT_PRINT_EOJ = 0x08, + + -- Header response data. + POS_DEVICE_END = 0x00, + NEG_COMMAND_REJECT = 0x00, + NEG_INTERVENTION_REQUIRED = 0x01, + NEG_OPERATION_CHECK = 0x02, + NEG_COMPONENT_DISCONNECTED = 0x03, + + -- TN3270E Negotiation Options + TN3270E_ASSOCIATE = 0x00, + TN3270E_CONNECT = 0x01, + TN3270E_DEVICE_TYPE = 0x02, + TN3270E_FUNCTIONS = 0x03, + TN3270E_IS = 0x04, + TN3270E_REASON = 0x05, + TN3270E_REJECT = 0x06, + TN3270E_REQUEST = 0x07, + TN3270E_SEND = 0x08, + + -- SFE Attributes + SFE_3270 = "192", + order_max = "\063", -- tn3270 orders can't be greater than 0x3F + COLS = 80, -- hardcoded width. + ROWS = 24, -- hardcoded rows. We only support 3270 model 2 which was 24x80. + buffer_addr = 1, + cursor_addr = 1, + isSSL = true, + + --- Creates a new TN3270 Client object + + new = function(self, socket) + local o = { + socket = socket or nmap.new_socket(), + -- TN3270 Buffers + buffer = {}, + fa_buffer = {}, + output_buffer = {}, + overwrite_buf = {}, + telnet_state = 0, -- same as TNS_DATA to begin with + server_options = {}, + client_options = {}, + unsupported_opts = {}, + sb_options = '', + connected_lu = '', + connected_dtype= '', + telnet_data = '', + tn_buffer = '', + negotiated = false, + first_screen = false, + state = 0, + buffer_address = 1, + formatted = false, + } + setmetatable(o, self) + self.__index = self + return o + end, + + --- Connects to a tn3270 servers + connect = function ( self, host, port ) + + local TN_PROTOCOLS = { "ssl", "tcp" } + local status, err + if not self.isSSL then + status, err = self.socket:connect(host, port, 'tcp') + local proto = 'tcp' + if status then + TN_PROTOCOLS = {proto} + return true + end + else + + for _, proto in pairs(TN_PROTOCOLS) do + status, err = self.socket:connect(host, port, proto) + if status then + TN_PROTOCOLS = {proto} + return true + end + stdnse.debug(3,"Can't connect using %s: %s", proto, err) + end + end + self.socket:close() + return false, err + end, + + disconnect = function ( self ) + stdnse.debug(2,"Disconnecting") + return self.socket:close() + end, + + recv_data = function ( self ) + return self.socket:receive() + end, + + close = function ( self ) + return self.socket:close() + end, + + send_data = function ( self, data ) + stdnse.debug(2, "Sending data: 0x%s", stdnse.tohex(data)) + return self.socket:send( data ) + end, + + ------------- End networking functions + + + -- TN3270 Helper functions + ----------- + --- Decode Buffer Address + -- + -- Buffer addresses can come in 14 or 12 (this terminal doesn't support 16 bit) + -- this function takes two bytes (buffer addresses are two bytes long) and returns + -- the decoded buffer address. + -- @param1 unsigned char, first byte of buffer address. + -- @param2 unsigned char, second byte of buffer address. + -- @return integer of buffer address + DECODE_BADDR = function ( byte1, byte2 ) + if (byte1 & 0xC0) == 0 then + -- (byte1 & 0x3F) << 8 | byte2 + return (((byte1 & 0x3F) << 8) | byte2) + else + -- (byte1 & 0x3F) << 6 | (byte2 & 0x3F) + return (((byte1 & 0x3F) << 6) | (byte2 & 0x3F)) + end + end, + + --- Encode Buffer Address + -- + -- @param integer buffer address + -- @return TN3270 encoded buffer address (12 bit) as string + ENCODE_BADDR = function ( self, address ) + stdnse.debug(3, "Encoding Address: %s", address) + return string.pack("BB", + -- (address >> 8) & 0x3F + -- we need the +1 because LUA tables start at 1 (yay!) + self.code_table[((address >> 6) & 0x3F)+1], + -- address & 0x3F + self.code_table[(address & 0x3F)+1] + ) + end, + + BA_TO_ROW = function ( self, addr ) + return math.ceil((addr / self.COLS) + 0.5) + end, + + BA_TO_COL = function ( self, addr ) + return addr % self.COLS + end, + + INC_BUF_ADDR = function ( self, addr ) + return ((addr + 1) % (self.COLS * self.ROWS)) + end, + + DEC_BUF_ADDR = function ( self, addr ) + return ((addr + 1) % (self.COLS * self.ROWS)) + end, + + --- Initiates tn3270 connection + initiate = function ( self, host, port ) + local status = true + --local status, err = self:connect(host , port) + local opts = {recv_before = true} + self.socket, self.telnet_data = comm.tryssl(host, port, '', opts) + + if ( not(self.socket) ) then + return false, self.telnet_data + end + -- clear out options buffers + self.client_options = {} + self.server_options = {} + self.state = self.NEGOTIATING + self.first_screen = false + + self:process_packets() -- process the first batch of information + -- then loop through until we're done negotiating telnet/tn3270 options + while not self.first_screen and status do + status, self.telnet_data = self:recv_data() + self:process_packets() + end + return status + end, + + --- rebuilds tn3270 screen based on information sent + -- Closes the socket if the mainframe has closed the socket on us + -- Is done reading when it encounters EOR + get_data = function ( self ) + local status = true + self.first_screen = false + while not self.first_screen and status do + status, self.telnet_data = self:recv_data() + self:process_packets() + end + if not status then + self:disconnect() + end + return status + end, + + get_all_data = function ( self, timeout ) + if timeout == nil then + timeout = 200 + end + local status = true + self.first_screen = false + self.socket:set_timeout(timeout) + while status do + status, self.telnet_data = self:recv_data() + if self.telnet_data ~= "TIMEOUT" then + self:process_packets() + end + end + self.socket:set_timeout(3000) + return status + end, + + process_packets = function ( self ) + for i = 1,#self.telnet_data,1 do + self:ts_processor(self.telnet_data:sub(i,i)) + end + -- once all the data has been processed we clear out the buffer + self.telnet_data = '' + end, + + --- Disable SSL + -- by default the tn3270 object uses SSL first. This disables SSL + disableSSL = function (self) + stdnse.debug(2,"Disabling SSL connections") + self.isSSL = false + end, + + --- Telnet State processor + -- + -- @return true if success false if encoutered any issues + + ts_processor = function ( self, data ) + local TNS_DATA = 0 + local TNS_IAC = 1 + local TNS_WILL = 2 + local TNS_WONT = 3 + local TNS_DO = 4 + local TNS_DONT = 5 + local TNS_SB = 6 + local TNS_SB_IAC = 7 + local supported = false + local DO_reply = self.commands.IAC .. self.commands.DO + local DONT_reply = self.commands.IAC .. self.commands.DONT + local WILL_reply = self.commands.IAC .. self.commands.WILL + local WONT_reply = self.commands.IAC .. self.commands.WONT + + --nsedebug.print_hex(data) + --stdnse.debug(3,"current state:%s", self.telnet_state) + + if self.telnet_state == TNS_DATA then + if data == self.commands.IAC then + -- got an IAC + self.telnet_state = TNS_IAC + return true + end + -- stdnse.debug("Adding 0x%s to Data Buffer", stdnse.tohex(data)) + self:store3270(data) + elseif self.telnet_state == TNS_IAC then + if data == self.commands.IAC then + -- insert this 0xFF in to the buffer + self:store3270(data) + self.telnet_state = TNS_DATA + elseif data == self.commands.EOR then + -- we're at the end of the TN3270 data + -- let's process it and see what we've got + -- but only if we're in 3270 mode + if self.state == self.TN3270_DATA or self.state == self.TN3270E_DATA then + self:process_data() + end + self.telnet_state = TNS_DATA + elseif data == self.commands.WILL then self.telnet_state = TNS_WILL + elseif data == self.commands.WONT then self.telnet_state = TNS_WONT + elseif data == self.commands.DO then self.telnet_state = TNS_DO + elseif data == self.commands.DONT then self.telnet_state = TNS_DONT + elseif data == self.commands.SB then self.telnet_state = TNS_SB + end + elseif self.telnet_state == TNS_WILL then + stdnse.debug(3, "[TELNET] IAC WILL 0x%s?", stdnse.tohex(data)) + for _,v in pairs(self.options) do -- check to see if we support this sub option (SB) + if v == data then + stdnse.debug(3, "[TELNET] IAC DO 0x%s", stdnse.tohex(data)) + supported = true + break + end + end -- end of checking options + for _,v in pairs(self.unsupported_opts) do + if v == data then + stdnse.debug(3, "[TELNET] IAC DONT 0x%s (disabled)", stdnse.tohex(data)) + supported = false + end + end + if supported then + if not self.server_options[data] then -- if we haven't already replied to this, let's reply + self.server_options[data] = true + self:send_data(DO_reply..data) + stdnse.debug(3, "[TELNET] Sent Will Reply: 0x%s", stdnse.tohex(data)) + self:in3270() + end + else + self:send_data(DONT_reply..data) + stdnse.debug(3, "[TELNET] Sent Don't Reply: 0x%s", stdnse.tohex(data)) + end + self.telnet_state = TNS_DATA + elseif self.telnet_state == TNS_WONT then + if self.server_options[data] then + self.server_options[data] = false + self:send_data(DONT_reply..data) + stdnse.debug(3, "[TELNET] Sent Don't Reply: 0x%s", stdnse.tohex(data)) + self:in3270() + end + self.telnet_state = TNS_DATA + elseif self.telnet_state == TNS_DO then + stdnse.debug(3, "[TELNET] IAC DO 0x%s?", stdnse.tohex(data)) + for _,v in pairs(self.options) do -- check to see if we support this sub option (SB) + if v == data then + stdnse.debug(3, "[TELNET] IAC WILL 0x%s", stdnse.tohex(data)) + supported = true + break + end + end -- end of checking options + for _,v in pairs(self.unsupported_opts) do + if v == data then + stdnse.debug(3, "[TELNET] IAC WONT 0x%s (disabled)", stdnse.tohex(data)) + supported = false + end + end + if supported then + if not self.client_options[data] then + self.client_options[data] = true + self:send_data(WILL_reply..data) + stdnse.debug(3, "[TELNET] Sent Do Reply: 0x%s" , stdnse.tohex(data)) + self:in3270() + end + else + self:send_data(WONT_reply..data) + stdnse.debug(3, "[TELNET] Got unsupported Do. Sent Won't Reply: %s %s", data, self.telnet_data) + end + self.telnet_state = TNS_DATA + elseif self.telnet_state == TNS_DONT then + if self.client_options[data] then + self.client_options[data] = false + self:send_data(WONT_reply .. data) + stdnse.debug(3, "[TELNET] Sent Wont Reply: 0x%s", stdnse.tohex(data)) + self:in3270() + end + self.telnet_state = TNS_DATA + elseif self.telnet_state == TNS_SB then + if data == self.commands.IAC then + self.telnet_state = TNS_SB_IAC + else + self.sb_options = self.sb_options .. data + end + elseif self.telnet_state == TNS_SB_IAC then + stdnse.debug(3, "[TELNET] Processing SB options") +-- self.sb_options = self.sb_options .. data -- looks like this is a bug? Why append F0 to the end? + if data == self.commands.SE then + self.telnet_state = TNS_DATA + if self.sb_options:sub(1,1) == self.options.TTYPE and + self.sb_options:sub(2,2) == self.commands.SEND then + self:send_data(self.commands.IAC .. + self.commands.SB .. + self.options.TTYPE .. + self.commands.IS .. + self.device_type .. + self.commands.IAC .. + self.commands.SE ) + elseif (self.client_options[self.options.TN3270] or self.client_options[self.options.TN3270E]) and + (self.sb_options:sub(1,1) == self.options.TN3270 or + self.sb_options:sub(1,1) == self.options.TN3270E) then + if not self:negotiate_tn3270() then + return false + end + stdnse.debug(3, "[TELNET] Done Negotiating Options") + else + self.telnet_state = TNS_DATA + end + self.sb_options = '' + end + --self.sb_options = '' + end -- end of makeshift switch/case + return true + end, + + --- Stores a character on a buffer to be processed + -- + store3270 = function ( self, char ) + self.tn_buffer = self.tn_buffer .. char + end, + + --- Function to negotiate TN3270 sub options + + negotiate_tn3270 = function ( self ) + stdnse.debug(3, "[TN3270] Processing tn data subnegotiation options ") + local option = self.sb_options:sub(2,2) + -- stdnse.debug("[TN3270E] We got this: 0x%s", stdnse.tohex(option)) + + if option == self.tncommands.SEND then + if self.sb_options:sub(3,3) == self.tncommands.DEVICETYPE then + if self.connected_lu == '' then + self:send_data(self.commands.IAC .. + self.commands.SB .. + self.options.TN3270E .. + self.tncommands.DEVICETYPE .. + self.tncommands.REQUEST .. + self.device_type .. + self.commands.IAC .. + self.commands.SE ) + else + stdnse.debug(3,"[TN3270] Sending LU: %s", self.connected_lu) + self:send_data(self.commands.IAC .. + self.commands.SB .. + self.options.TN3270E .. + self.tncommands.DEVICETYPE .. + self.tncommands.REQUEST .. + self.device_type .. + self.tncommands.CONNECT .. + self.connected_lu .. + self.commands.IAC .. + self.commands.SE ) + end + else + stdnse.debug(3,"[TN3270] Received TN3270 Send but not device type. Weird.") + return false + end + elseif option == self.tncommands.DEVICETYPE then -- Mainframe is confirming device type. Good! + if self.sb_options:sub(3,3) == self.tncommands.REJECT then + -- Welp our LU request failed, shut it down + stdnse.debug(3,"[TN3270] Received TN3270 REJECT.") + return false + elseif self.sb_options:sub(3,3) == self.tncommands.IS then + local tn_loc = 1 + while self.sb_options:sub(4+tn_loc,4+tn_loc) ~= self.commands.SE and + self.sb_options:sub(4+tn_loc,4+tn_loc) ~= self.tncommands.CONNECT do + tn_loc = tn_loc + 1 + end + --XXX Unused variable??? Should this be tn_loc? + -- local sn_loc = 1 + if self.sb_options:sub(4+tn_loc,4+tn_loc) == self.tncommands.CONNECT then + self.connected_lu = self.sb_options:sub(5+tn_loc, #self.sb_options) + self.connected_dtype = self.sb_options:sub(4,3+tn_loc) + stdnse.debug(3,"[TN3270] Current LU: %s", self.connected_lu) + end + -- since We've connected lets send our options + self:send_data(self.commands.IAC .. + self.commands.SB .. + self.options.TN3270E .. + self.tncommands.FUNCTIONS .. + self.tncommands.REQUEST .. + --self.tncommands.RESPONSES .. -- we'll only support basic 3270E mode + self.commands.IAC .. + self.commands.SE ) + end + elseif option == self.tncommands.FUNCTIONS then + if self.sb_options:sub(3,3) == self.tncommands.IS then + -- they accepted the function request, lets move on + self.negotiated = true + stdnse.debug(3,"[TN3270] Option Negotiation Done!") + self:in3270() + elseif self.sb_options:sub(3,3) == self.tncommands.REQUEST then + -- dummy functions for now. Our client doesn't have any + -- functions really but we'll agree to whatever they want + self:send_data(self.commands.IAC .. + self.commands.SB .. + self.options.TN3270E .. + self.tncommands.FUNCTIONS .. + self.tncommands.IS .. + self.sb_options:sub(4,4) .. + self.commands.IAC .. + self.commands.SE ) + self.negotiated = true + self:in3270() + end + end + return true + end, + + --- Check to see if we're in TN3270 + in3270 = function ( self ) + if self.client_options[self.options.TN3270E] then + stdnse.debug(3,"[in3270] In TN3270E mode") + if self.negotiated then + stdnse.debug(3,"[in3270] TN3270E negotiated") + self.state = self.TN3270E_DATA + end + elseif self.client_options[self.options.EOR] and + self.client_options[self.options.BINARY] and + self.client_options[self.options.EOR] and + self.client_options[self.options.BINARY] and + self.client_options[self.options.TTYPE] then + stdnse.debug(3,"[in3270] In TN3270 mode") + self.state = self.TN3270_DATA + end + + if self.state == self.TN3270_DATA or self.state == self.TN3270E_DATA then + -- since we're in TN3270 mode, let's create an empty buffer + stdnse.debug(3, "[in3270] Creating Empty IBM-3278-2 Buffer") + for i=0, 1920 do + self.buffer[i] = "\0" + self.fa_buffer[i] = "\0" + self.overwrite_buf[i] = "\0" + end + stdnse.debug(3, "[in3270] Empty Buffer Created. Length: %d", #self.buffer) + end + stdnse.debug(3,"[in3270] Current State: %s", self.word_state[self.state]) + end, + + --- Also known as process_eor + process_data = function ( self ) + local reply = 0 + stdnse.debug(3,"Processing TN3270 Data") + if self.state == self.TN3270E_DATA then + stdnse.debug(3,"[TN3270E] Processing TN3270 Data header: %s", stdnse.tohex(self.tn_buffer:sub(1,5))) + self.tn3270_header.data_type = self.tn_buffer:sub(1,1) + self.tn3270_header.request_flag = self.tn_buffer:sub(2,2) + self.tn3270_header.response_flag = self.tn_buffer:sub(3,3) + self.tn3270_header.seq_number = self.tn_buffer:sub(4,5) + if self.tn3270_header.data_type == "\000" then + reply = self:process_3270(self.tn_buffer:sub(6)) + stdnse.debug(3,"[TN3270E] Reply: %s", reply) + end + if reply < 0 and self.tn3270_header.request_flag ~= self.TN3270E_RSF_NO_RESPONSE then + self:tn3270e_nak(reply) + elseif reply == self.NO_OUTPUT and + self.tn3270_header.request_flag == self.ALWAYS_RESPONSE then + self:tn3270e_ack() + end + else + self:process_3270(self.tn_buffer) + end + -- nsedebug.print_hex(self.tn_buffer) + + self.tn_buffer = '' + return true + end, + + tn3270e_nak = function ( self, reply ) + local neg = "" + if reply == self.BAD_COMMAND then + neg = self.NEG_COMMAND_REJECT + elseif reply == self.BAD_ADDRESS then + neg = self.NEG_OPERATION_CHECK + end + + -- build the TN3270E nak reply header + local reply_buf = string.pack("BBB c2", + self.DT_RESPONSE, -- type + 0, -- request + self.NEGATIVE_RESPONSE, -- response + -- because this is telnet we gotta double up 0xFF chars + self.tn3270_header.seq_number:sub(1,2):gsub(self.commands.IAC, self.commands.IAC:rep(2)) + ) .. neg .. self.commands.IAC .. self.commands.EOR + + -- now send the whole thing + self:send_data(reply_buf) + end, + + tn3270e_ack = function ( self ) + -- build the TN3270E ack reply header + local reply_buf = string.pack("BBB c2", + self.DT_RESPONSE, -- type + 0, -- request + self.POSITIVE_RESPONSE, -- response + -- because this is telnet we gotta double up 0xFF chars + self.tn3270_header.seq_number:sub(1,2):gsub(self.commands.IAC, self.commands.IAC:rep(2)) + ) .. self.POS_DEVICE_END .. self.commands.IAC .. self.commands.EOR + -- now send the whole package + self:send_data(reply_buf) + end, + + clear_screen = function ( self ) + self.buffer_address = 1 + for i=1,1920,1 do + self.buffer[i] = "\0" + self.fa_buffer[i] = "\0" + end + end, + + clear_unprotected = function ( self ) + -- we don't support protect vs unprotected (yet) + -- this function is a stub for now + end, + + process_3270 = function ( self, data ) + -- the first byte will be the command we have to follow + local com = data:sub(1,1) + stdnse.debug(3, "[PROCESS 3270] Value Received: 0x%s", stdnse.tohex(com)) + if com == self.command.EAU then + stdnse.debug(3,"TN3270 Command: Erase All Unprotected") + self:clear_unprotected() + return self.NO_OUTPUT + elseif com == self.command.EWA or com == self.sna_command.EWA or + com == self.command.EW or com == self.sna_command.EW then + stdnse.debug(3,"TN3270 Command: Erase Write (Alternate)") + self:clear_screen() + self:process_write(data) -- so far should only return No Output + return self.NO_OUTPUT + elseif com == self.command.W or com == self.sna_command.W then + stdnse.debug(3,"TN3270 Command: Write") + self:process_write(data) + elseif com == self.command.RB or com == self.sna_command.RB then + stdnse.debug(3,"TN3270 Command: Read Buffer") + self:process_read() + return self.OUTPUT + elseif com == self.command.RM or com == self.sna_command.RM or + com == self.command.RMA or com == self.sna_command.RMA then + stdnse.debug(3,"TN3270 Command: Read Modified (All)") + --XXX What is read_modified? What is aid? + --self:read_modified(aid) + --return self.OUTPUT + stdnse.debug1("UNIMPLEMENTED TN3270 Command: Read Modified (All)") + return self.BAD_COMMAND + elseif com == self.command.WSF or com == self.sna_command.WSF then + stdnse.debug(3,"TN3270 Command: Write Structured Field") + return self:w_structured_field(data) + elseif com == self.command.NOP or com == self.sna_command.NOP then + stdnse.debug(3,"TN3270 Command: No OP (NOP)") + return self.NO_OUTPUT + else + stdnse.debug(3,"Unknown 3270 Data Stream command: 0x%s", stdnse.tohex(com)) + return self.BAD_COMMAND + + end + return 1 -- we may sometimes enter a state where we have nothing which is fine + + end, + + --- WCC / tn3270 data stream processor + -- + -- @param tn3270 data stream + -- @return status true on success, false on failure + -- @return changes self.buffer to match requested changes + process_write = function ( self, data ) + stdnse.debug(3, "Processing TN3270 Write Command") + local prev = '' + local cp = '' + local num_attr = 0 + local last_cmd = false + local i = 2 -- skip SF to get WCC + + if (data:byte(i) & 0x40) == 0x40 then + stdnse.debug(3,"[WCC] Reset") + end + + if (data:byte(i) & 0x02) == 0x02 then + stdnse.debug(3,"[WCC] Restore") + end + + i = 3 -- skip the SF and the WCC. + while i <= #data do + cp = data:sub(i,i) + stdnse.debug(4,"Current Position: %d of %d", i, #data) + stdnse.debug(4,"Current Item: %s", stdnse.tohex(cp)) + -- yay! lua has no switch statement + if cp == self.orders.SF then + stdnse.debug(4,"Start Field") + prev = 'ORDER' + + last_cmd = true + i = i + 1 -- skip SF + stdnse.debug(4,"Writing Zero to buffer at address: %s", self.buffer_address) + stdnse.debug(4,"Attribute Type: 0x%s", stdnse.tohex(data:sub(i,i))) + self:write_field_attribute(data:sub(i,i)) + self:write_char("\00") + self.buffer_address = self:INC_BUF_ADDR(self.buffer_address) + -- set the current position one ahead (after SF) + i = i + 1 + + elseif cp == self.orders.SFE then + stdnse.debug(4,"Start Field Extended") + i = i + 1 -- skip SFE + num_attr = data:byte(i) + stdnse.debug(4,"Number of Attributes: %d", num_attr) + for j = 1,num_attr do + i = i + 1 + if data:byte(i) == 0xc0 then + stdnse.debug(4,"Writing Zero to buffer at address: %s", self.buffer_address) + stdnse.debug(4,"Attribute Type: 0x%s", stdnse.tohex(data:sub(i,i))) + self:write_char("\00") + self:write_field_attribute(data:sub(i,i)) + end + + i = i + 1 + end + i = i + 1 + self.buffer_address = self:INC_BUF_ADDR(self.buffer_address) + elseif cp == self.orders.SBA then + stdnse.debug(4,"Set Buffer Address (SBA) 0x11") + self.buffer_address = self.DECODE_BADDR(data:byte(i+1), data:byte(i+2)) + stdnse.debug(4,"Buffer Address: %s", self.buffer_address) + stdnse.debug(4,"Row: %s", self:BA_TO_ROW(self.buffer_address)) + stdnse.debug(4,"Col: %s", self:BA_TO_COL(self.buffer_address)) + last_cmd = true + prev = 'SBA' + -- the current position is SBA, the next two bytes are the lengths + i = i + 3 + stdnse.debug(4,"Next Command: %s", stdnse.tohex(data:sub(i,i))) + elseif cp == self.orders.IC then -- Insert Cursor + stdnse.debug(4,"Insert Cursor (IC) 0x13") + stdnse.debug(4,"Current Cursor Address: %s", self.cursor_addr) + stdnse.debug(4,"Buffer Address: %s", self.buffer_address) + stdnse.debug(4,"Row: %s", self:BA_TO_ROW(self.buffer_address)) + stdnse.debug(4,"Col: %s", self:BA_TO_COL(self.buffer_address)) + prev = 'ORDER' + self.cursor_addr = self.buffer_address + last_cmd = true + i = i + 1 + elseif cp == self.orders.RA then + -- Repeat address repeats whatever the next char is after the two byte buffer address + -- There's all kinds of weird GE stuff we could do, but not now. Maybe in future vers + stdnse.debug(4,"Repeat to Address (RA) 0x3C") + local ra_baddr = self.DECODE_BADDR(data:byte(i+1), data:byte(i+2)) + stdnse.debug(4,"Repeat Character: %s", stdnse.tohex(data:sub(i+1,i+2))) + + stdnse.debug(4,"Repeat to this Address: %s", ra_baddr) + stdnse.debug(4,"Current Address: %s", self.buffer_address) + prev = 'ORDER' + --char_code = data:sub(i+3,i+3) + i = i + 3 + local char_to_repeat = data:sub(i,i) + stdnse.debug(4,"Repeat Character: %s", stdnse.tohex(char_to_repeat)) + while (self.buffer_address ~= ra_baddr) do + self:write_char(char_to_repeat) + self.buffer_address = self:INC_BUF_ADDR(self.buffer_address) + end + elseif cp == self.orders.EUA then + stdnse.debug(4,"Erase Unprotected All (EAU) 0x12") + local eua_baddr = self.DECODE_BADDR(data:byte(i+1), data:byte(i+2)) + i = i + 3 + stdnse.debug(4,"EAU to this Address: %s", eua_baddr) + stdnse.debug(4,"Current Address: %s", self.buffer_address) + while (self.buffer_address ~= eua_baddr) do + -- do nothing for now. this feature isn't supported/required at the moment + self.buffer_address = self:INC_BUF_ADDR(self.buffer_address) + --stdnse.debug(3,"Current Address: %s", self.buffer_address) + --stdnse.debug(3,"EAU to this Address: %s", eua_baddr) + end + elseif cp == self.orders.GE then + stdnse.debug(4,"Graphical Escape (GE) 0x08") + prev = 'ORDER' + i = i + 1 -- move to next byte + local ge_char = data:sub(i,i) + self:write_char(self, ge_char) + self.buffer_address = self:INC_BUF_ADDR(self.buffer_address) + elseif cp == self.orders.MF then + -- MotherFucker, lol! + -- or mainframe maybe + -- we don't actually have 'fields' at this point + -- so there's nothing to be modified + stdnse.debug(4,"Modify Field (MF) 0x2C") + prev = 'ORDER' + i = i + 1 + local num_attr = tonumber(data:sub(i,i)) + for j = 1, num_attr, 1 do + -- placeholder in case we need to do something here + stdnse.debug(4,"Set Attribute (MF) 0x2C") + i = i + 1 + end + self.buffer_address = self:INC_BUF_ADDR(self.buffer_address) + elseif cp == self.orders.SA then + -- We'll add alerting here to identify hidden field + -- but for now we're doing NOTHING + i = i + 1 + + elseif cp == self.fcorders.NUL or + cp == self.fcorders.SUB or + cp == self.fcorders.DUP or + cp == self.fcorders.FM or + cp == self.fcorders.FF or + cp == self.fcorders.CR or + cp == self.fcorders.NL or + cp == self.fcorders.EM or + cp == self.fcorders.EO then + stdnse.debug(4,"Format Control Order received") + prev = 'ORDER' + self:write_char("\064") + self.buffer_address = self:INC_BUF_ADDR(self.buffer_address) + i = i + 1 + else -- whoa we made it. + local ascii_char = drda.StringUtil.toASCII(cp) + stdnse.debug(4,"Inserting 0x"..stdnse.tohex(cp).." (".. ascii_char ..") at the following location:") + stdnse.debug(4,"Row: %s", self:BA_TO_ROW(self.buffer_address)) + stdnse.debug(4,"Col: %s", self:BA_TO_COL(self.buffer_address)) + stdnse.debug(4,"Buffer Address: %s", self.buffer_address) + self:write_char(data:sub(i,i)) + self.buffer_address = self:INC_BUF_ADDR(self.buffer_address) + self.first_screen = true + i = i + 1 + end -- end of massive if/else + end -- end of while loop + self.formatted = true + end, + + write_char = function ( self, char ) + if self.buffer[self.buffer_address] == "\0" then + self.buffer[self.buffer_address] = char + else + self.overwrite_buf[self.buffer_address] = self.buffer[self.buffer_address] + self.buffer[self.buffer_address] = char + end + end, + + write_field_attribute = function ( self, attr ) + self.fa_buffer[self.buffer_address] = attr + end, + + process_read = function ( self ) + local output_addr = 0 + self.output_buffer = {} + stdnse.debug(3,"Generating Read Buffer") + self.output_buffer[output_addr] = string.pack("B",self.aid) + output_addr = output_addr + 1 + stdnse.debug(3,"Output Address: %s", output_addr) + self.output_buffer[output_addr] = self:ENCODE_BADDR(self.cursor_addr) + return self:send_tn3270(self.output_buffer) + + -- need to add while loop + + end, + + w_structured_field = function ( self, wsf_data ) + -- this is the ugliest hack ever + -- but it works and it doesn't matter what we support anyway + stdnse.debug(3, "Processing TN3270 Write Structured Field Command") + -- all our options, one liner style + local query_options = + "\x88\x00\x16\x81\x86\x00\x08\x00\xf4\xf1\x00\xf2\x00\xf3\x00\xf4\z + \x00\xf5\x00\xf6\x00\xf7\x00\x00\x0d\x81\x87\x04\x00\xf0\xf1\xf1\z + \xf2\xf2\xf4\xf4\x00\x22\x81\x85\x82\x00\x07\x10\x00\x00\x00\x00\z + \x07\x00\x00\x00\x00\x65\x00\x25\x00\x00\x00\x02\xb9\x00\x25\x01\z + \x00\xf1\x03\xc3\x01\x36\x00\x2e\x81\x81\x03\x00\x00\x50\x00\x18\z + \x00\x00\x01\x00\x48\x00\x01\x00\x48\x07\x10\x00\x00\x00\x00\x00\z + \x00\x13\x02\x00\x01\x00\x50\x00\x18\x00\x00\x01\x00\x48\x00\x01\z + \x00\x48\x07\x10\x00\x1c\x81\xa6\x00\x00\x0b\x01\x00\x00\x50\x00\z + \x18\x00\x50\x00\x18\x0b\x02\x00\x00\x07\x00\x10\x00\x07\x00\x10\z + \x00\x07\x81\x88\x00\x01\x02\x00\x16\x81\x80\x80\x81\x84\x85\x86\z + \x87\x88\xa1\xa6\xa8\x96\x99\xb0\xb1\xb2\xb3\xb4\xb6\x00\x08\x81\z + \x84\x00\x0a\x00\x04\x00\x06\x81\x99\x00\x00\xff\xef" + stdnse.debug(3, "Current WSF : %s", stdnse.tohex(wsf_data:sub(4,4)) ) + + if self.state == self.TN3270E_DATA then + -- We need to add the header here since we're in TN3270E mode + query_options = "\x00\x00\x00\x00\x00" .. query_options + end + self:send_data(query_options) + return 1 + end, + + + --- Sends TN3270 Packet + -- + -- Expands IAC to IAC IAC and finally appends IAC EOR + -- @param data: table containing buffer array + send_tn3270 = function ( self, data ) + local packet = '' + if self.state == self.TN3270E_DATA then + packet = "\x00\x00\x00\x00\x00" + -- we need to create the tn3270E (the E is important) header + -- which, in basic 3270E is 5 bytes of 0x00 + --packet = string.pack("BBB >I2", + -- self.DT_3270_DATA, -- type + -- 0, -- request + -- 0, -- response + -- 0 + -- ) + --self.tn3270_header.seq_number + end + -- create send buffer and double up IACs + + for i=0,#data do + stdnse.debug(3,"Adding 0x" .. stdnse.tohex(data[i]) .. " to the read buffer") + packet = packet .. data[i] + if data[i] == self.commands.IAC then + packet = packet .. self.commands.IAC + end + end + packet = packet .. self.commands.IAC .. self.commands.EOR + return self:send_data(packet) -- send the output buffer + end, + + get_screen = function ( self ) + stdnse.debug(3,"Returning the current TN3270 buffer") + local buff = '\n' + for i = 0,#self.buffer do + if self.buffer[i] == "\00" then + buff = buff .. " " + else + buff = buff .. drda.StringUtil.toASCII(self.buffer[i]) + end + if (i+1) % 80 == 0 then + buff = buff .. "\n" + end + end + return buff + end, + + get_screen_debug = function ( self, lvl ) + lvl = lvl or 1 + stdnse.debug(lvl,"---------------------- Printing the current TN3270 buffer ----------------------") + local buff = '' + for i = 0,#self.buffer do + if self.buffer[i] == "\00" then + buff = buff .. " " + else + buff = buff .. drda.StringUtil.toASCII(self.buffer[i]) + end + if (i+1) % 80 == 0 then + stdnse.debug(lvl, buff) + buff = '' + end + end + stdnse.debug(lvl,"----------------------- End of the current TN3270 buffer ---------------------") + + return buff + end, + + get_screen_raw = function ( self ) + local buff = '' + for i = 0,#self.buffer do + buff = buff .. drda.StringUtil.toASCII(self.buffer[i]) + end + return buff + end, + + + --- Sends data at the current cursor location. Ignores field attributes. + -- + -- It only uses enter key (AID = 0x7d) to send this data + -- for more complicated items use send_location + -- @param string you wish to send. + send_cursor = function ( self, data ) + local ebcdic_letter = '' + self.output_buffer = {} -- empty the output buffer + self.output_buffer[0] = string.pack("B",self.aids.ENTER) -- what follows is an ENTER + stdnse.debug(3,"Cursor Location ("..self.cursor_addr.."): Row: %s, Column: %s ", + self:BA_TO_ROW(self.cursor_addr), + self:BA_TO_COL(self.cursor_addr) ) + table.insert(self.output_buffer, self:ENCODE_BADDR(self.cursor_addr + #data)) -- location of cursor + table.insert(self.output_buffer, self.orders.SBA) -- set the buffer address to the following location + table.insert(self.output_buffer, self:ENCODE_BADDR(self.cursor_addr)) -- location of buffer address + for i = 1, #data do + stdnse.debug(3,"Inserting %s to the send buffer", data:sub(i,i)) + ebcdic_letter = drda.StringUtil.toEBCDIC( data:sub(i,i) ) + table.insert(self.output_buffer, ebcdic_letter ) -- insert the ebcdic character on the array + end + return self:send_tn3270(self.output_buffer) + end, + + --- Sends the data to the location specified + -- + -- Using a location on the screen sends the data + -- @param location: a location on the screen (between 0 and 1920) + -- @param data: ascii data to send to that location + send_location = function( self, location, data ) + local cursor_location = location + #data + local ebcdic_letter = '' + self.output_buffer = {} + self.output_buffer[0] = string.pack("B",self.aids.ENTER) + table.insert(self.output_buffer, self:ENCODE_BADDR(cursor_location)) + stdnse.debug(3,"Cursor Location ("..cursor_location.."): Row: %s, Column: %s ", + self:BA_TO_ROW(cursor_location), + self:BA_TO_COL(cursor_location) ) + stdnse.debug(3,"Inserting %s at location %d", data, location ) + table.insert(self.output_buffer, self.orders.SBA) + cursor_location = location + table.insert(self.output_buffer, self:ENCODE_BADDR(cursor_location)) + for j = 1, #data do + ebcdic_letter = drda.StringUtil.toEBCDIC( data:sub(j,j) ) + table.insert(self.output_buffer, ebcdic_letter ) + end + + return self:send_tn3270(self.output_buffer) + end, + + --- Sends the data to multiple locations on the screen + -- + -- Using a supplied tuple of location and data generates tn3270 data to + -- fill out the screen + -- @param location_tuple: and array of tuples with location and data. For + -- example: send_locations([{579:"dade"},{630:"secret"}]) + send_locations = function( self, location_tuple ) + local cursor_location = location_tuple[#location_tuple][1] + #location_tuple[#location_tuple][2] + local ebcdic_letter = '' + self.output_buffer = {} + self.output_buffer[0] = string.pack("B",self.aids.ENTER) + table.insert(self.output_buffer, self:ENCODE_BADDR(cursor_location)) + stdnse.debug(3,"Cursor Location ("..cursor_location.."): Row: %s, Column: %s ", + self:BA_TO_ROW(cursor_location), + self:BA_TO_COL(cursor_location) ) + for i = 1, #location_tuple do + stdnse.debug(3,"Inserting %s at location %d", location_tuple[i][2], location_tuple[i][1] ) + table.insert(self.output_buffer, self.orders.SBA) + cursor_location = location_tuple[i][1] + table.insert(self.output_buffer, self:ENCODE_BADDR(cursor_location)) + for j = 1, #location_tuple[i][2] do + ebcdic_letter = drda.StringUtil.toEBCDIC( location_tuple[i][2]:sub(j,j) ) + table.insert(self.output_buffer, ebcdic_letter ) + end + end + return self:send_tn3270(self.output_buffer) + end, + + send_enter = function ( self ) + local ebcdic_letter = '' + self.output_buffer = {} + self.output_buffer[0] = string.pack("B",self.aids.ENTER) + table.insert(self.output_buffer, self:ENCODE_BADDR(self.cursor_addr)) + table.insert(self.output_buffer, self.orders.SBA) + table.insert(self.output_buffer, self:ENCODE_BADDR(self.cursor_addr)) + return self:send_tn3270(self.output_buffer) + end, + + send_clear = function ( self ) + return self:send_data( string.pack("B",self.aids.CLEAR) .. self.commands.IAC .. self.commands.EOR ) + end, + + send_pf = function ( self, pf ) + if pf > 24 or pf < 0 then + return false, "PF Value must be between 1 and 24" + end + self.output_buffer = {} + self.output_buffer[0] = string.pack("B", self.aids["PF"..pf] ) + stdnse.debug(3,"Cursor Location ("..self.cursor_addr.."): Row: %s, Column: %s ", + self:BA_TO_ROW(self.cursor_addr), + self:BA_TO_COL(self.cursor_addr) ) + self.output_buffer[1] = self:ENCODE_BADDR(self.cursor_addr) + return self:send_tn3270(self.output_buffer) + end, + + writeable = function (self) + -- Returns a list with all writeable fields as {location, length} tuples + local writeable_list = {} + for i = 0,#self.fa_buffer do + if ( self.fa_buffer[i] ~= "\00" ) and (self.fa_buffer[i]:byte(1) & 0x20) ~= 0x20 then + -- found writeable flag + for j = i,#self.fa_buffer do + -- find end of field + if (self.fa_buffer[j]:byte(1) & 0x20) == 0x20 then + stdnse.debug(3,"[WRITEABLE] Area: %d Row: %d Col: %d Length: %d", i + 1, self:BA_TO_ROW(i + 1), self:BA_TO_COL(i + 2), j-i-1) + table.insert(writeable_list, {i + 1, j-i-1}) + break + end + end + end + end + return writeable_list + end, + + find = function ( self, str ) + local buff = '' + for i = 0,#self.buffer do + if self.buffer[i] == "\00" then + buff = buff .. " " + else + buff = buff .. drda.StringUtil.toASCII(self.buffer[i]) + end + end + --local buff = self:get_screen() + stdnse.debug(3, "[FIND] Looking for: %s", tostring(str)) + local i, j = string.find(buff, str) + if i == nil then + stdnse.debug(3, "[FIND] Couldn't find: %s", tostring(str)) + return false + else + stdnse.debug(3, "[FIND] Found String: %s", tostring(str)) + return i , j + end + end, + + isClear = function ( self ) + local buff = '' + for i = 0,#self.buffer do + if self.buffer[i] == "\00" then + buff = buff .. " " + else + buff = buff .. drda.StringUtil.toASCII(self.buffer[i]) + end + end + local i, j = string.find(buff, '%w') + if i ~= nil then + stdnse.debug(3, "[CLEAR] Screen has text") + return false + else + stdnse.debug(3, "[CLEAR] Screen is Empty") + return true + end + end, + + --- Any Hidden Fields + -- + -- @returns true if there are any hidden fields in the buffer + any_hidden = function ( self ) + local hidden_attrib = 0x0c -- 00001100 is hidden + for i = 0,#self.fa_buffer do + if (self.fa_buffer[i]:byte(1) & hidden_attrib) == hidden_attrib then + return true + end + end + end, + + --- Hidden Fields + -- + -- @returns the locations of hidden fields in a table with each pair being the start and stop of the hidden field + hidden_fields_location = function ( self ) + local hidden_attrib = 0x0c -- 00001100 is hidden + local hidden_location = {} + local i = 1 + if not self:any_hidden() then + return hidden_location + end + while i <= #self.fa_buffer do + if (self.fa_buffer[i]:byte(1) & hidden_attrib) == hidden_attrib then + stdnse.debug(3, "Found hidden field at buffer location: " .. i) + table.insert(hidden_location, i) + i = i + 1 + while self.fa_buffer[i] == "\0" do + i = i + 1 + end + table.insert(hidden_location, i) + end + i = i + 1 + end + return hidden_location + end, + + hidden_fields = function ( self ) + local locations = self:hidden_fields_location() + local fields = {} + local i, j = 1,1 + local start, stop = 0 + while i <= #locations do + start = locations[i] + 1 + stop = locations[i+1] - 1 + stdnse.debug(3, "Start Location: %i Stop Location %i", start, stop) + fields[j] = '' + for k = start,stop do + -- stdnse.debug(3, "k = %i Inserting 0x%s", k, stdnse.tohex(self.buffer[k])) + fields[j] = fields[j] .. drda.StringUtil.toASCII(self.buffer[k]) + end + j = j + 1 + i = i + 2 + end + return fields + end, + + any_overwritten = function ( self ) + for i = 1, #self.overwrite_buf do + if self.overwrite_buf[i] ~= "\0" then + return true + end + end + return false + end, + + set_lu = function (self, LU) + -- Sets an LU + self.connected_lu = LU + end, + + get_lu = function ( self ) + return self.connected_lu + end, + disable_tn3270e = function ( self ) + stdnse.debug(3,"Disabling TN3270E") + table.insert(self.unsupported_opts,self.options.TN3270E) + end, + overwrite_data = function ( self ) + if not self:any_overwritten() then + return false + end + stdnse.debug(3,"Printing the overwritten TN3270 buffer") + local buff = '\n' + for i = 0,#self.overwrite_buf do + if self.overwrite_buf[i] == "\0" then + buff = buff .. " " + else + buff = buff .. drda.StringUtil.toASCII(self.buffer[i]) + end + if i % 80 == 0 then + buff = buff .. "\n" + end + end + return buff + end +} + +return _ENV |