summaryrefslogtreecommitdiffstats
path: root/nselib/drda.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/drda.lua')
-rw-r--r--nselib/drda.lua775
1 files changed, 775 insertions, 0 deletions
diff --git a/nselib/drda.lua b/nselib/drda.lua
new file mode 100644
index 0000000..9ef74a0
--- /dev/null
+++ b/nselib/drda.lua
@@ -0,0 +1,775 @@
+---
+-- DRDA Library supporting a very limited subset of operations.
+--
+-- Summary
+-- * The library currently provides functionality to: (1) Query the server for
+-- basic settings using the <code>getServerInfo</code> function of the helper
+-- class. (2) Authenticate to a DB2 server using a plain-text username and
+-- password.
+--
+-- The library contains the following classes:
+-- * <code>DRDA</code>
+-- ** Implements the Distributed Relational Database Architecture class .
+-- * <code>DRDAParameter</code>
+-- ** Implements a number of functions to handle DRDA parameters.
+-- * <code>DDM</code>
+-- ** Implements the DDM portion of the DRDA structure
+-- * <code>Command</code>
+-- ** Provides functions for easy creation of the most common DRDAs.
+-- ** Implemented as a static class that returns an instance of the DRDA.
+-- * <code>Helper</code>
+-- ** A helper class that provides easy access to the rest of the library
+-- * <code>DB2Socket</code>
+-- ** A smallish socket wrapper that provides fundamental buffering
+-- * <code>StringUtil</code>
+-- ** Provides EBCDIC/ASCII conversion functions
+-- * <code>Comm</code>
+-- ** Provides fundamental communication support (send/receive DRDAPacket)
+-- * <code>DRDAPacket</code>
+-- ** A class holding one or more DRDA's and provides some basic access methods
+--
+-- The following sample code illustrates how scripts can use the Helper class
+-- to interface with the library:
+--
+-- <code>
+-- db2helper = drda.Helper:new()
+-- status, err = db2helper:connect(host, port)
+-- status, res = db2helper:getServerInfo()
+-- status, err = db2helper:close()
+-- </code>
+--
+-- The implementation is based on packet dumps and the excellent decoding
+-- provided by Wireshark.
+--
+-- There is some documentation at
+-- http://publib.boulder.ibm.com/infocenter/dzichelp/v2r2/topic/com.ibm.db29.doc.drda/db2z_drda.htm.
+--
+-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
+-- @author Patrik Karlsson <patrik@cqure.net>
+--
+
+--
+-- Version 0.2
+-- Created 05/08/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
+-- Revised 07/27/2010 - v0.2 - Added the comm class and made a few improvements
+-- to sending and receiving packets. Changed the
+-- helper login method to support:
+-- x IBM DB2
+-- x Apache Derby
+-- x IBM Informix Dynamic Server
+
+local match = require "match"
+local nmap = require "nmap"
+local stdnse = require "stdnse"
+local string = require "string"
+local table = require "table"
+_ENV = stdnse.module("drda", stdnse.seeall)
+
+-- CodePoint constants
+CodePoint = {
+ CODEPNT = 0x000c,
+ TYPDEFNAM = 0x002f,
+ TYPDEFOVR = 0x0035,
+ ACCSEC = 0x106d,
+ SECCHK = 0x106e,
+ EXCSAT = 0x1041,
+ PRDID = 0x112e,
+ SRVCLSNM = 0x1147,
+ SVRCOD = 0x1149,
+ SYNERRCD = 0x114a,
+ SRVRLSLV = 0x115a,
+ EXTNAM = 0x115e,
+ SRVNAM = 0x116d,
+ USRID = 0x11a0,
+ PASSWORD = 0x11a1,
+ SECMEC = 0x11a2,
+ SECCHKCD = 0x11a4,
+ SECCHKRM = 0x1219,
+ SYNTAXRM = 0x124c,
+ MGRLVLLS = 0x1404,
+ EXCSATRD = 0x1443,
+ ACCSECRD = 0x14ac,
+ ACCRDB = 0x2001,
+ PRDDATA = 0x2104,
+ RDBACCL = 0x210f,
+ RDBNAM = 0x2110,
+ CRRTKN = 0x2135,
+ ACCRDBRM = 0x2201,
+ RDBNFNRM = 0x2211,
+ RDBAFLRM = 0x221a,
+ RDBATHRM = 0x22cb,
+}
+
+-- Security Mechanism
+SecMec =
+{
+ USER_PASSWORD = 0x0003,
+ USER_ONLY = 0x0004,
+ CHANGE_PASSWORD = 0x0005,
+ USER_PASS_SUBST = 0x0006,
+ USER_ENC_PASS = 0x0007,
+ ENC_USER_ENC_PASS = 0x0009,
+ ENC_CHANGE_PASS = 0x000A,
+ KERBEROS = 0x000B,
+ ENC_USER_DATA = 0x000C,
+ ENC_USER_ENC_PASS_ENC_DATA = 0x000D,
+ ENC_USER_ENC_PASS_ENC_NEWPASS_ENC_DATA = 0x000E,
+}
+
+DRDAPacket = {
+
+ new = function(self, drda_array)
+ local o = {
+ drda_array = drda_array,
+ count = #drda_array
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ getDRDAByCodePoint = function( self, codepoint )
+ for i=1, #self.drda_array do
+ if ( self.drda_array[i].DDM.CodePoint == codepoint ) then
+ return self.drda_array[i]
+ end
+ end
+ end,
+
+ getDRDA = function( self, n )
+ return ( #self.drda_array >= n ) and self.drda_array[n] or nil
+ end,
+
+ __tostring = function( self )
+ local data = ""
+ -- do some DDM fixup in here
+ for i=1, #self.drda_array do
+ if ( i == 1 and #self.drda_array > 1 ) then
+ self.drda_array[1].DDM.Format = 0x41
+ else
+ self.drda_array[i].DDM.Format = 0x01
+ end
+ self.drda_array[i].DDM.CorelId = i
+ data = data .. tostring(self.drda_array[i])
+ end
+ return data
+ end
+
+}
+
+-- Distributed Relational Database Architecture (DRDA) Class
+DRDA = {
+
+ new = function(self, ddm)
+ local o = {
+ Parameters = {},
+ DDM = ddm
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --- Sets the DDM
+ --
+ -- @param ddm DDM to assign to the DRDA
+ -- @return status boolean true on success, false on failure
+ setDDM = function( self, ddm )
+ if ( not(ddm) ) then
+ return false, "DDM cannot be nil"
+ end
+ self.DDM = ddm
+ return true
+ end,
+
+ --- Adds a DRDA parameter to the table
+ --
+ -- @param param DRDAParam containing the parameter to add to the table
+ -- @return status bool true on success, false on failure
+ -- @return err string containing the error message if status is false
+ addParameter = function( self, param )
+ if ( not(self.DDM) ) then
+ stdnse.debug1("drda.DRDA.addParameter: DDM must be set prior to adding parameters")
+ return false, "DDM must be set prior to adding parameters"
+ end
+ if ( not(param) ) then
+ stdnse.debug1("drda.DRDA.addParameter: Param cannot be nil")
+ return false, "Param cannot be nil"
+ end
+
+ table.insert(self.Parameters, param)
+
+ -- update the DDM length fields
+ self.DDM.Length = self.DDM.Length + param.Length
+ self.DDM.Length2 = self.DDM.Length2 + param.Length
+
+ return true
+ end,
+
+ --- Gets a parameter from the DRDA parameter table
+ --
+ -- @param codepoint number containing the parameter type ro retrieve
+ -- @return param DRDAParameter containing the requested parameter
+ getParameter = function( self, codepoint )
+ for _, v in ipairs( self.Parameters ) do
+ if ( v.CodePoint == codepoint ) then
+ return v
+ end
+ end
+ return
+ end,
+
+ --- Converts the DRDA class to a string
+ --
+ -- @return data containing the object instance
+ __tostring = function(self)
+ if ( not(self.DDM) ) then
+ stdnse.debug1("drda.DRDA.toString: DDM cannot be nil")
+ return nil
+ end
+
+ local data = {
+ string.pack(">I2BBI2I2I2", self.DDM.Length, self.DDM.Magic, self.DDM.Format, self.DDM.CorelId, self.DDM.Length2, self.DDM.CodePoint )
+ }
+ for k,v in ipairs(self.Parameters) do
+ data[#data+1] = tostring(v)
+ end
+ return table.concat(data)
+ end,
+
+ --- Sends the DRDA over the db2socket
+ --
+ -- @param db2socket DB2Socket over which to send the data
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ send = function( self, db2socket )
+ return db2socket:send( tostring(self) )
+ end,
+
+ --- Receives data from the db2socket and builds a DRDA object
+ --
+ -- @param db2socket from which to read data
+ -- @return Status (true or false).
+ -- @return Data (if status is true) or error string (if status is false).
+ receive = function( self, db2socket )
+ local DDM_SIZE = 10
+ local pos = 1
+
+ -- first read atleast enough so that we can populate the DDM
+ local status, data = db2socket:receive_buf( match.numbytes(DDM_SIZE), true )
+ if ( not(status) ) then
+ stdnse.debug1("drda.DRDA.receive: %s", data)
+ return false, ("Failed to read at least %d bytes from socket"):format(DDM_SIZE)
+ end
+
+ local ddm = DDM:new()
+ ddm:fromString( data )
+ self:setDDM( ddm )
+
+ status, data = db2socket:receive_buf( match.numbytes(ddm.Length - 10), true )
+ if ( not(status) ) then
+ return false, ("Failed to read the remaining %d bytes of the DRDA message")
+ end
+
+ -- add parameters until pos reaches the "end"
+ repeat
+ local param = DRDAParameter:new()
+ pos = param:fromString( data, pos )
+ self:addParameter( param )
+ until ( #data <= pos )
+
+ return true
+ end,
+
+}
+
+-- The DRDAParameter class implements the DRDA parameters
+DRDAParameter = {
+
+ --- DRDA Parameter constructor
+ --
+ -- @param codepoint number containing the codepoint value
+ -- @param data string containing the data portion of the DRDA parameter
+ -- @return o DRDAParameter object
+ new = function(self, codepoint, data)
+ local o = {
+ CodePoint = codepoint,
+ Data = data,
+ Length = ( data and #data + 4 or 4 )
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --- Converts the DRDA Parameter object to a string
+ --
+ -- @return data string containing the DRDA Parameter
+ __tostring = function( self )
+ return string.pack(">I2I2", self.Length, self.CodePoint) .. (self.Data or "")
+ end,
+
+ --- Builds a DRDA Parameter from a string
+ --
+ -- @param data string from which to build the DRDA Parameter
+ -- @param pos number containing the offset into data
+ -- @return pos the new position after processing, -1 on error
+ fromString = function( self, data, pos )
+ if( #data < 4 ) then
+ return -1
+ end
+ self.Length, self.CodePoint, pos = string.unpack( ">I2I2", data, pos )
+
+ if ( self.Length > 0 ) then
+ self.Data, pos = string.unpack("c" .. self.Length - 4, data, pos )
+ end
+ return pos
+ end,
+
+ --- Returns the data portion of the parameter as an ASCII string
+ --
+ -- @return str containing the data portion of the DRDA parameter as ASCII
+ getDataAsASCII = function( self )
+ return StringUtil.toASCII( self.Data )
+ end,
+
+ --- Returns the data in EBCDIC format
+ --
+ -- @return str containing the data portion of the DRDA parameter in EBCDIC
+ getData = function( self )
+ return self.Data
+ end,
+
+}
+
+-- Distributed data management (DDM)
+DDM = {
+
+ Formats =
+ {
+ RESERVED = 0x80,
+ CHAINED = 0x40,
+ CONTINUE = 0x20,
+ SAME_CORRELATION = 0x10,
+ },
+
+ Length = 10,
+ Magic = 0xD0,
+ Format = 0x41,
+ CorelId = 1,
+ Length2 = 4,
+ CodePoint = 0,
+
+ --- Creates a new DDM packet
+ --
+ -- @param codepoint
+ -- @param format
+ -- @param corelid
+ -- @return DDM object
+ new = function(self, codepoint, format, corelid)
+ local o = {
+ CodePoint = codepoint,
+ Format = format,
+ CorelId = corelid
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --- Converts the DDM object to a string
+ __tostring = function( self )
+ return string.pack(">I2BBI2I2I2", self.Length, self.Magic, self.Format, self.CorelId, self.Length2, self.CodePoint)
+ end,
+
+ --- Constructs a DDM object from a string
+ --
+ -- @param str containing the data from which to construct the object
+ fromString = function( self, str )
+ local DDM_SIZE = 10
+ local pos = 1
+
+ if ( #str < DDM_SIZE ) then
+ return -1, ("drda.DDM.fromString: str was less than DDM_SIZE (%d)"):format( DDM_SIZE )
+ end
+
+ self.Length, self.Magic, self.Format, self.CorelId, self.Length2, self.CodePoint, pos = string.unpack( ">I2BBI2I2I2", str )
+ return pos
+ end,
+
+ --- Verifiers if there are additional DRDA's following
+ --
+ -- @return true if the DRDA is to be chained, false if it's the last one
+ isChained = function( self )
+ if ( (self.Format & DDM.Formats.CHAINED) == DDM.Formats.CHAINED ) then
+ return true
+ end
+ return false
+ end,
+
+ --- Set the DRDA as chained (more following)
+ --
+ -- @param chained boolean true if more DRDA's are following
+ setChained = function( self, chained )
+ if ( self:isChained() ) then
+ self.Format = ( self.Format ~ self.Formats.CHAINED )
+ else
+ self.Format = ( self.Format | self.Formats.CHAINED )
+ end
+ end,
+
+}
+
+-- static DRDA packet construction class
+Command =
+{
+ --- Builds an EXCSAT DRDA packet
+ --
+ -- @param extname string containing the external name
+ -- @param srvname string containing the server name
+ -- @param rellev string containing the server product release level
+ -- @param mgrlvlls string containing the manager level list
+ -- @param srvclass string containing the server class name
+ -- @return drda DRDA instance
+ EXCSAT = function( extname, srvname, rellev, mgrlvlls, srvclass )
+ local drda = DRDA:new( DDM:new( CodePoint.EXCSAT ) )
+
+ drda:addParameter( DRDAParameter:new( CodePoint.EXTNAM, StringUtil.toEBCDIC( extname ) ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.SRVNAM, StringUtil.toEBCDIC( srvname ) ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.SRVRLSLV, StringUtil.toEBCDIC( rellev ) ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.MGRLVLLS, mgrlvlls ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.SRVCLSNM, StringUtil.toEBCDIC( srvclass ) ) )
+
+ return drda
+ end,
+
+ --- Builds an ACCSEC DRDA packet
+ --
+ -- @param secmec number containing the security mechanism ID
+ -- @param database string containing the database name
+ -- @return drda DRDA instance
+ ACCSEC = function( secmec, database )
+ local drda = DRDA:new( DDM:new( CodePoint.ACCSEC ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.SECMEC, secmec ))
+ drda:addParameter( DRDAParameter:new( CodePoint.RDBNAM, StringUtil.toEBCDIC(StringUtil.padWithChar(database,' ', 18)) ))
+
+ return drda
+ end,
+
+ --- Builds a SECCHK DRDA packet
+ --
+ -- @param secmec number containing the security mechanism ID
+ -- @param database string containing the database name
+ -- @param username string
+ -- @param password string
+ -- @return drda DRDA instance
+ SECCHK = function( secmec, database, username, password )
+ local drda = DRDA:new( DDM:new( CodePoint.SECCHK ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.SECMEC, secmec ))
+ drda:addParameter( DRDAParameter:new( CodePoint.RDBNAM, StringUtil.toEBCDIC(StringUtil.padWithChar(database,' ', 18)) ))
+ drda:addParameter( DRDAParameter:new( CodePoint.USRID, StringUtil.toEBCDIC(username) ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.PASSWORD, StringUtil.toEBCDIC(password) ) )
+
+ return drda
+ end,
+
+ --- Builds an ACCRDB DRDA packet
+ --
+ -- @param database string containing the database name
+ -- @param rdbaccl string containing the RDB access manager class
+ -- @param prdid string containing the product id
+ -- @param typdefnam string containing the data type definition name
+ -- @param typdefovr string containing the data type definition override
+ -- @return drda DRDA instance
+ ACCRDB = function( database, rdbaccl, prdid, prddata, typdefnam, crrtkn, typdefovr )
+ local drda = DRDA:new( DDM:new( CodePoint.ACCRDB ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.RDBNAM, StringUtil.toEBCDIC(StringUtil.padWithChar(database,' ', 18)) ) )
+
+ if ( rdbaccl ) then
+ drda:addParameter( DRDAParameter:new( CodePoint.RDBACCL, rdbaccl ) )
+ end
+ if ( prdid ) then
+ drda:addParameter( DRDAParameter:new( CodePoint.PRDID, StringUtil.toEBCDIC( prdid ) ) )
+ end
+ if ( prddata ) then
+ drda:addParameter( DRDAParameter:new( CodePoint.PRDDATA, StringUtil.toEBCDIC( prddata ) ) )
+ end
+ if( typdefnam ) then
+ drda:addParameter( DRDAParameter:new( CodePoint.TYPDEFNAM, StringUtil.toEBCDIC( typdefnam ) ) )
+ end
+ if( crrtkn ) then
+ drda:addParameter( DRDAParameter:new( CodePoint.CRRTKN, crrtkn ) )
+ end
+ if( typdefovr ) then
+ drda:addParameter( DRDAParameter:new( CodePoint.TYPDEFOVR, typdefovr ) )
+ end
+
+ return drda
+ end
+
+}
+
+
+-- Helper Class
+Helper = {
+
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --- Connect to the DB2 host
+ --
+ -- @param host table
+ -- @param port table
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ connect = function( self, host, port )
+ self.comm = Comm:new( host, port )
+ return self.comm:connect()
+ end,
+
+ --- Closes an open connection.
+ --
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ close = function( self )
+ self.comm:close()
+ end,
+
+ --- Returns Server Information (name, platform, version)
+ --
+ -- @return table containing <code>extname</code>, <code>srvclass</code>,
+ -- <code>srvname</code> and <code>prodrel</code>
+ getServerInfo = function( self )
+ local mgrlvlls = stdnse.fromhex("1403000724070008240f00081440000814740008")
+ local drda_excsat = Command.EXCSAT( "", "", "", mgrlvlls, "" )
+ local response, param, err
+
+ local status, packet = self.comm:exchDRDAPacket( DRDAPacket:new( { drda_excsat } ) )
+ if ( not(status) ) then return false, err end
+
+ local drda = packet:getDRDAByCodePoint( CodePoint.EXCSATRD )
+ if ( drda ) then
+ response = {}
+ param = drda:getParameter( CodePoint.EXTNAM )
+ if ( param ) then
+ response.extname = param:getDataAsASCII()
+ end
+ param = drda:getParameter( CodePoint.SRVCLSNM )
+ if ( param ) then
+ response.srvclass = param:getDataAsASCII()
+ end
+ param = drda:getParameter( CodePoint.SRVNAM )
+ if ( param ) then
+ response.srvname = param:getDataAsASCII()
+ end
+ param = drda:getParameter( CodePoint.SRVRLSLV )
+ if ( param ) then
+ response.prodrel = param:getDataAsASCII()
+ end
+ else
+ return false, "The response contained no EXCSATRD"
+ end
+
+ return true, response
+ end,
+
+ --- Login to DB2 database server
+ --
+ -- @param database containing the name of the database
+ -- @param username containing the authentication username
+ -- @param password containing the authentication password
+ -- @return Status (true or false)
+ -- @return err message (if status if false)
+ login = function( self, database, username, password )
+ local mgrlvlls = stdnse.fromhex("1403000724070008240f00081440000814740008")
+ local secmec, prdid = "\00\03", "JCC03010"
+ local tdovr = stdnse.fromhex("0006119c04b80006119d04b00006119e04b8")
+ local crrtkn= stdnse.fromhex("d5c6f0f0f0f0f0f14bc3c6f4c4012a11168414")
+
+ local drda_excsat = Command.EXCSAT( "", "", "", mgrlvlls, "" )
+ local drda_accsec = Command.ACCSEC( secmec, database )
+ local drda_secchk = Command.SECCHK( secmec, database, username, password )
+ local drda_accrdb = Command.ACCRDB( database, "\x24\x07", "DNC10060", nil, "QTDSQLASC", crrtkn, tdovr)
+
+ local status, packet = self.comm:exchDRDAPacket( DRDAPacket:new( { drda_excsat, drda_accsec } ) )
+ if( not(status) ) then return false, packet end
+
+ if ( packet:getDRDAByCodePoint( CodePoint.RDBNFNRM ) or
+ packet:getDRDAByCodePoint( CodePoint.RDBAFLRM ) ) then
+ stdnse.debug1("drda.Helper.login: ERROR: RDB not found")
+ return false, "ERROR: Database not found"
+ end
+
+ local drda = packet:getDRDAByCodePoint( CodePoint.ACCSECRD )
+ if ( not(drda) ) then
+ return false, "ERROR: Response did not contain any valid security mechanisms"
+ end
+
+ local param = drda:getParameter( CodePoint.SECMEC )
+ if ( not(param) ) then
+ stdnse.debug1("drda.Helper.login: ERROR: Response did not contain any valid security mechanisms")
+ return false, "ERROR: Response did not contain any valid security mechanisms"
+ end
+
+ if ( string.unpack(">I2", param:getData()) ~= SecMec.USER_PASSWORD ) then
+ stdnse.debug1("drda.Helper.login: ERROR: Securite Mechanism not supported")
+ return false, "ERROR: Security mechanism not supported"
+ end
+
+ status, packet = self.comm:exchDRDAPacket( DRDAPacket:new( { drda_secchk, drda_accrdb } ) )
+ if( not(status) ) then return false, "ERROR: Login failed" end
+
+ --
+ -- At this point we have a few differences in behaviour
+ -- * DB2 has told us earlier if the DB does not exist
+ -- * Apache Derby will do so here, regardless of the login was
+ -- successful or not
+ -- * Informix will tell us that the DB does not exist IF the
+ -- login was successful
+ --
+ -- Therefore the order of these checks are important!!
+ if ( packet:getDRDAByCodePoint( CodePoint.ACCRDBRM ) ) then
+ return true
+ -- Apache Derby responds differently with usernames containing spaces
+ elseif ( packet:getDRDAByCodePoint( CodePoint.RDBATHRM ) ) then
+ return false, "ERROR: Login failed"
+ -- Informix responds with a SECCHKRM DDM response
+ elseif ( packet:getDRDAByCodePoint( CodePoint.SECCHKRM ) ) then
+ drda = packet:getDRDAByCodePoint( CodePoint.SECCHKRM )
+ param= drda:getParameter( CodePoint.SECCHKCD )
+ if ( param and param:getData() == "\0" ) then
+ return true
+ end
+ elseif ( packet:getDRDAByCodePoint( CodePoint.RDBNFNRM ) or
+ packet:getDRDAByCodePoint( CodePoint.RDBAFLRM ) ) then
+ return false, "ERROR: Database not found"
+ end
+ return false, "ERROR: Login failed"
+ end,
+
+}
+
+-- The communication class
+Comm = {
+
+ new = function(self, host, port)
+ local o = {
+ host = host,
+ port = port,
+ socket = nmap.new_socket()
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ connect = function(self)
+ return self.socket:connect(self.host, self.port)
+ end,
+
+ close = function(self)
+ return self.socket:close()
+ end,
+
+ recvDRDA = function( self )
+ local drda_tbl = {}
+
+ repeat
+ local drda = DRDA:new()
+ local status, err = drda:receive( self.socket )
+ if ( not(status) ) then
+ return false, err
+ end
+ table.insert(drda_tbl, drda)
+ until ( not(drda.DDM:isChained()) )
+ return true, drda_tbl
+ end,
+
+ --- Sends a packet to the server and receives the response
+ --
+ -- @param DRDAPacket
+ -- @return status true on success, false on failure
+ -- @return packet an instance of DRDAPacket
+ exchDRDAPacket = function( self, packet )
+ local drda, err
+ local status, err = self.socket:send( tostring(packet) )
+
+ if ( not(status) ) then
+ stdnse.debug1("drda.Helper.login: ERROR: DB2Socket error: %s", err )
+ return false, ("ERROR: DB2Socket error: %s"):format( err )
+ end
+
+ status, drda = self:recvDRDA()
+ if( not(status) ) then
+ stdnse.debug1("drda.Helper.login: ERROR: DB2Socket error: %s", drda )
+ return false, ("ERROR: DB2Socket error: %s"):format( drda )
+ end
+ return true, DRDAPacket:new( drda )
+ end
+
+}
+
+-- EBCDIC/ASCII Conversion tables
+a2e_hex = "00010203372D2E2F1605250B0C0D0E0F101112133C3D322618193F271C1D1E1F\z
+405A7F7B5B6C507D4D5D5C4E6B604B61F0F1F2F3F4F5F6F7F8F97A5E4C7E6E6F\z
+7CC1C2C3C4C5C6C7C8C9D1D2D3D4D5D6D7D8D9E2E3E4E5E6E7E8E9ADE0BD5F6D\z
+79818283848586878889919293949596979899A2A3A4A5A6A7A8A9C04FD0A107\z
+202122232415061728292A2B2C090A1B30311A333435360838393A3B04143EE1\z
+4142434445464748495152535455565758596263646566676869707172737475\z
+767778808A8B8C8D8E8F909A9B9C9D9E9FA0AAABAC4AAEAFB0B1B2B3B4B5B6B7\z
+B8B9BABBBC6ABEBFCACBCCCDCECFDADBDCDDDEDFEAEBECEDEEEFFAFBFCFDFEFF"
+
+e2a_hex = "000102039C09867F978D8E0B0C0D0E0F101112139D8508871819928F1C1D1E1F\z
+80818283840A171B88898A8B8C050607909116939495960498999A9B14159E1A\z
+20A0A1A2A3A4A5A6A7A8D52E3C282B7C26A9AAABACADAEAFB0B121242A293B5E\z
+2D2FB2B3B4B5B6B7B8B9E52C255F3E3FBABBBCBDBEBFC0C1C2603A2340273D22\z
+C3616263646566676869C4C5C6C7C8C9CA6A6B6C6D6E6F707172CBCCCDCECFD0\z
+D17E737475767778797AD2D3D45BD6D7D8D9DADBDCDDDEDFE0E1E2E3E45DE6E7\z
+7B414243444546474849E8E9EAEBECED7D4A4B4C4D4E4F505152EEEFF0F1F2F3\z
+5C9F535455565758595AF4F5F6F7F8F930313233343536373839FAFBFCFDFEFF"
+
+-- Creates the lookup tables needed for conversion
+a2e_tbl = stdnse.fromhex(a2e_hex)
+e2a_tbl = stdnse.fromhex(e2a_hex)
+
+-- Handle EBCDIC/ASCII conversion
+StringUtil =
+{
+ --- Converts an ASCII string to EBCDIC
+ --
+ -- @param ascii string containing the ASCII value
+ -- @return string containing the EBCDIC value
+ toEBCDIC = function( ascii )
+ return string.gsub(ascii, ".", function(a)
+ local val = a:byte() + 1
+ return a2e_tbl:sub(val, val)
+ end)
+ end,
+
+ --- Converts an EBCDIC string to ASCII
+ --
+ -- @param ebcdic string containing EBCDIC value
+ -- @return string containing ASCII value
+ toASCII = function( ebcdic )
+ return string.gsub(ebcdic, ".", function(e)
+ local val = e:byte() + 1
+ return e2a_tbl:sub(val, val)
+ end)
+ end,
+
+ --- Pads a string with a character
+ --
+ -- @param str string to pad
+ -- @param chr char to pad with
+ -- @param len the total length of the finished string
+ -- @return str string containing the padded string
+ padWithChar = function( str, chr, len )
+ return str .. string.rep(chr, len - #str)
+ end,
+}
+
+return _ENV;