1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 12:41:29 +00:00
Files
nmap/nselib/mssql.lua
david a6e014d42e Change these script arguments to use stdnse.parse_timespec:
qscan.delay
dns-fuzz.timelimit
mssql.timelimit
A side effect is that the default units for qscan.delay are seconds, not
milliseconds. 0 is now the magic value to disable the time limit in
dns-fuzz.
2010-04-13 23:09:23 +00:00

1043 lines
30 KiB
Lua

--- MSSQL Library supporting a very limited subset of operations
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
--
-- @author = "Patrik Karlsson <patrik@cqure.net>"
--
-- Summary
-- -------
-- The library was designed and tested against Microsoft SQL Server 2005.
-- However, it should work with versions 7.0, 2000, 2005 and 2008.
-- Only a minimal amount of parsers have been added for tokens, column types
-- and column data in order to support the first scripts.
--
-- The code has been implemented based on traffic analysis and the following
-- documentation:
-- o TDS Protocol Documentation
-- http://www.freetds.org/tds.html
--
-- o The JTDS source code
-- http://jtds.sourceforge.net/index.html
--
-- Overview
-- --------
-- o ColumInfo - Class containing parsers for column types which are present
-- before the row data in all query response packets. The column
-- information contains information relevant to the data type
-- used to hold the data eg. precision, character sets, size etc.
--
-- o ColumnData - Class containing parsers for the actual column information
--
-- o Token - Class containing parsers for tokens returned in all TDS responses.
-- A server response may hold one or more tokens with information
-- from the server. Each token has a type which has a number of
-- type specific fields.
--
-- o QueryPacket - Class used to hold a query and convert it to a string
-- suitable for transmission over a socket.
--
-- o LoginPacket - Class used to hold login specific data which can easily
-- be converted to a string suitable for transmission over
-- a socket.
--
-- o TDSStream - Class that handles communication over the Tabular Data Stream
-- protocol used by SQL serve. It is used to transmit the the
-- Query- and Login-packets to the server.
--
-- o Helper - Class which facilitates the use of the library by through action
-- oriented functions with descriptive names.
--
-- o Util - "static" class containing mostly character and type conversion
-- functions.
--
-- Example
-- -------
-- The following sample code illustrates how scripts can use the Helper class
-- to interface the library:
--
-- <code>
-- local helper = mssql.Helper:new()
-- status, result = helper:Login( username, password, "temdpb", host.ip )
-- status, result = helper:Query( "SELECT name FROM master..syslogins")
-- helper:Disconnect()
-- <code>
--
-- Known limitations
-- -----------------
-- o The library does not support SSL. The foremost reason being the akward
-- choice of implementation where the SSL handshake is performed within
-- the TDS data block. By default, servers support connections over non
-- SSL connections though.
--
-- o Version 7 and ONLY version 7 of the protocol is supported. This should
-- cover Microsoft SQL Server 7.0 and later.
--
-- o TDS Responses contain one or more response tokens which are parsed based
-- on their type. The supported tokens are listed in the TokenType table and
-- their respective parsers can be found in the Token class. Note that some
-- token parsers are not fully implemented and simply move the offset the
-- right number of bytes to continue processing of the response.
--
-- o The library only supports a limited subsets of datatypes and will abort
-- execution and return an error if it detects an unsupported type. The
-- supported data types are listed in the DataTypes table. In order to add
-- additional data types a parser function has to be added to both the
-- ColumnInfo and ColumnData class.
--
-- o No functionality for languages, localization or characted codepages has
-- been considered or implemented.
--
-- o The library does database authentication only. No OS authentication or
-- use of the integrated security model is supported.
--
-- o Queries using SELECT, INSERT, DELETE and EXEC of procedures have been
-- tested while developing scripts.
--
--
--
-- @args mssql.timeout How long to wait for SQL responses. This is a number
-- followed by <code>ms</code> for milliseconds, <code>s</code> for seconds,
-- <code>m</code> for minutes, or <code>h</code> for hours. Default:
-- <code>30s</code>.
--
-- Version 0.2
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 03/28/2010 - v0.2 - fixed incorrect token types. added 30 seconds timeout
--
--
module(... or "mssql", package.seeall)
require("bit")
require("bin")
require("stdnse")
do
local arg = nmap.registry.args and nmap.registry.args["mssql.timeout"] or "30s"
local timeout, err
timeout, err = stdnse.parse_timespec(arg)
if not timeout then
error(err)
end
MSSQL_TIMEOUT = timeout
end
-- TDS packet types
PacketType =
{
Query = 0x01,
Response = 0x04,
Login = 0x10,
}
-- TDS response token types
TokenType =
{
TDS7Results = 0x81,
ErrorMessage = 0xAA,
InformationMessage = 0xAB,
LoginAcknowledgement = 0xAD,
Row = 0xD1,
OrderBy = 0xA9,
EnvironmentChange = 0xE3,
Done = 0xFD,
DoneInProc = 0xFF,
}
-- SQL Server/Sybase data types
DataTypes =
{
SYBINTN = 0x26,
SYBINT2 = 0x34,
SYBINT4 = 0x38,
SYBDATETIME = 0x3D,
SYBDATETIMN = 0x6F,
XSYBVARBINARY = 0xA5,
XSYBVARCHAR = 0xA7,
XSYBNVARCHAR = 0xE7,
}
-- "static" ColumInfo parser class
ColumnInfo =
{
Parse =
{
[DataTypes.XSYBNVARCHAR] = function( data, pos )
local colinfo = {}
local tmp
pos, colinfo.lts, colinfo.codepage, colinfo.flags, colinfo.charset,
colinfo.msglen = bin.unpack("<SSSCC", data, pos )
pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos)
colinfo.text = Util.FromWideChar(tmp)
return pos, colinfo
end,
[DataTypes.SYBINT2] = function( data, pos )
return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos)
end,
[DataTypes.SYBINTN] = function( data, pos )
local colinfo = {}
local tmp
pos, colinfo.unknown, colinfo.msglen = bin.unpack("<CC", data, pos)
pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos )
colinfo.text = Util.FromWideChar(tmp)
return pos, colinfo
end,
[DataTypes.SYBINT4] = function( data, pos )
return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos)
end,
[DataTypes.XSYBVARBINARY] = function( data, pos )
local colinfo = {}
local tmp
pos, colinfo.lts, colinfo.msglen = bin.unpack("<SC", data, pos)
pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos )
colinfo.text = Util.FromWideChar(tmp)
return pos, colinfo
end,
[DataTypes.SYBDATETIME] = function( data, pos )
local colinfo = {}
local tmp
pos, colinfo.msglen = bin.unpack("C", data, pos)
pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos )
colinfo.text = Util.FromWideChar(tmp)
return pos, colinfo
end,
[DataTypes.SYBDATETIMN] = function( data, pos )
return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
end,
[DataTypes.XSYBVARCHAR] = function( data, pos )
return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
end,
}
}
-- "static" ColumData parser class
ColumnData =
{
Parse = {
[DataTypes.XSYBNVARCHAR] = function( data, pos )
local size, coldata
pos, size = bin.unpack( "<S", data, pos )
pos, coldata = bin.unpack( "A"..size, data, pos )
return pos, Util.FromWideChar(coldata)
end,
[DataTypes.XSYBVARCHAR] = function( data, pos )
local size, coldata
pos, size = bin.unpack( "<S", data, pos )
pos, coldata = bin.unpack( "A"..size, data, pos )
return pos, coldata
end,
[DataTypes.XSYBVARBINARY] = function( data, pos )
local coldata, size
pos, size = bin.unpack( "<S", data, pos )
pos, coldata = bin.unpack( "A"..size, data, pos )
return pos, "0x" .. select(2, bin.unpack("H"..coldata:len(), coldata ) )
end,
[DataTypes.SYBINT4] = function( data, pos )
local num
pos, num = bin.unpack("<I", data, pos)
return pos, num
end,
[DataTypes.SYBINT2] = function( data, pos )
local num
pos, num = bin.unpack("<S", data, pos)
return pos, num
end,
[DataTypes.SYBINTN] = function( data, pos )
local len, num
pos, len = bin.unpack("C", data, pos)
if ( len == 1 ) then
return bin.unpack("C", data, pos)
elseif ( len == 2 ) then
return bin.unpack("<S", data, pos)
elseif ( len == 4 ) then
return bin.unpack("<I", data, pos)
elseif ( len == 8 ) then
return bin.unpack("<L", data, pos)
else
return -1, ("Unhandled length (%d) for SYBINTN"):format(len)
end
return -1, "Error"
end,
[DataTypes.SYBDATETIME] = function( data, pos )
local hi, lo, dt, result
pos, hi, lo = bin.unpack("<II", data, pos)
-- CET 01/01/1900
dt = -2208996000
result = os.date("%x %X", dt + (hi*24*60*60) + (lo/300) )
return pos, result
end,
[DataTypes.SYBDATETIMN] = function( data, pos )
return ColumnData.Parse[DataTypes.SYBINTN]( data, pos )
end,
}
}
-- "static" Token parser class
Token =
{
Parse = {
--- Parse error message tokens
--
-- @param data string containing "raw" data
-- @param pos number containing offset into data
-- @return pos number containing new offset after parse
-- @return token table containing token specific fields
[TokenType.ErrorMessage] = function( data, pos )
local token = {}
local tmp
token.type = TokenType.ErrorMessage
pos, token.size, token.errno, token.state, token.severity, token.errlen = bin.unpack( "<SICCS", data, pos )
pos, tmp = bin.unpack("A" .. (token.errlen * 2), data, pos )
token.error = Util.FromWideChar(tmp)
pos, token.srvlen = bin.unpack("C", data, pos)
pos, tmp = bin.unpack("A" .. (token.srvlen * 2), data, pos )
token.server = Util.FromWideChar(tmp)
pos, token.proclen = bin.unpack("C", data, pos)
pos, tmp = bin.unpack("A" .. (token.proclen * 2), data, pos )
token.proc = Util.FromWideChar(tmp)
pos, token.lineno = bin.unpack("<S", data, pos)
return pos, token
end,
--- Parse environment change tokens
-- (This function is not implemented and simply moves the pos offset)
--
-- @param data string containing "raw" data
-- @param pos number containing offset into data
-- @return pos number containing new offset after parse
-- @return token table containing token specific fields
[TokenType.EnvironmentChange] = function( data, pos )
local token = {}
local tmp
token.type = TokenType.EnvironmentChange
pos, token.size = bin.unpack("<S", data, pos)
return pos + token.size, token
end,
--- Parse information message tokens
--
-- @param data string containing "raw" data
-- @param pos number containing offset into data
-- @return pos number containing new offset after parse
-- @return token table containing token specific fields
[TokenType.InformationMessage] = function( data, pos )
local pos, token = Token.Parse[TokenType.ErrorMessage]( data, pos )
token.type = TokenType.InformationMessage
return pos, token
end,
--- Parse login acknowledgment tokens
--
-- @param data string containing "raw" data
-- @param pos number containing offset into data
-- @return pos number containing new offset after parse
-- @return token table containing token specific fields
[TokenType.LoginAcknowledgement] = function( data, pos )
local token = {}
local _
-- don't do much, just increase the pos offset to next token
token.type = TokenType.LoginAcknowledgement
pos, token.size, _, _, _, _, token.textlen = bin.unpack( "<SCCCSC", data, pos )
pos, token.text = bin.unpack("A" .. token.textlen * 2, data, pos)
pos, token.version = bin.unpack("<I", data, pos )
return pos, token
end,
--- Parse done tokens
--
-- @param data string containing "raw" data
-- @param pos number containing offset into data
-- @return pos number containing new offset after parse
-- @return token table containing token specific fields
[TokenType.Done] = function( data, pos )
local token = {}
local _
-- don't do much, just increase the pos offset to next token
token.type = TokenType.Done
pos, token.flags, token.operation, token.rowcount = bin.unpack( "<SSI", data, pos )
return pos, token
end,
--- Parses a DoneInProc token recieved after executing a SP
--
-- @param data string containing "raw" data
-- @param pos number containing offset into data
-- @return pos number containing new offset after parse
-- @return token table containing token specific fields
[TokenType.DoneInProc] = function( data, pos )
local token
pos, token = Token.Parse[TokenType.Done]( data, pos )
token.type = TokenType.DoneInProc
return pos, token
end,
--- Parses a OrderBy token
--
-- @param data string containing "raw" data
-- @param pos number containing offset into data
-- @return pos number containing new offset after parse
-- @return token table containing token specific fields
[TokenType.OrderBy] = function( data, pos )
local token = {}
pos, token.size = bin.unpack("<S", data, pos)
token.type = TokenType.OrderBy
return pos + token.size, token
end,
--- Parse TDS result tokens
--
-- @param data string containing "raw" data
-- @param pos number containing offset into data
-- @return pos number containing new offset after parse
-- @return token table containing token specific fields
[TokenType.TDS7Results] = function( data, pos )
local token = {}
local _
token.type = TokenType.TDS7Results
pos, token.count = bin.unpack( "<S", data, pos )
token.colinfo = {}
for i=1, token.count do
local colinfo = {}
local usertype, flags, ttype
pos, usertype, flags, ttype = bin.unpack("<SSC", data, pos )
if ( not(ColumnInfo.Parse[ttype]) ) then
return -1, ("Unhandled data type: 0x%X"):format(ttype)
end
pos, colinfo = ColumnInfo.Parse[ttype]( data, pos )
colinfo.usertype = usertype
colinfo.flags = flags
colinfo.type = ttype
table.insert( token.colinfo, colinfo )
end
return pos, token
end,
},
--- Parses the first token at positions pos
--
-- @param data string containing "raw" data
-- @param pos number containing offset into data
-- @return pos number containing new offset after parse or -1 on error
-- @return token table containing token specific fields or error message on error
ParseToken = function( data, pos )
local ttype
pos, ttype = bin.unpack("C", data, pos)
if ( not(Token.Parse[ttype]) ) then
return -1, ("No parser for token type: 0x%X"):format( ttype )
end
return Token.Parse[ttype](data, pos)
end,
}
--- QueryPacket class
QueryPacket =
{
new = function(self,o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end,
SetQuery = function( self, query )
self.query = query
end,
--- Returns the query packet as string
--
-- @return string containing the authentication packet
ToString = function( self )
return PacketType.Query, Util.ToWideChar( self.query )
end,
}
--- LoginPacket class
LoginPacket =
{
-- options_1 possible values
-- 0x80 enable warning messages if SET LANGUAGE issued
-- 0x40 change to initial database must succeed
-- 0x20 enable warning messages if USE <database> issued
-- 0x10 enable BCP
-- options_2 possible values
-- 0x80 enable domain login security
-- 0x40 "USER_SERVER - reserved"
-- 0x20 user type is "DQ login"
-- 0x10 user type is "replication login"
-- 0x08 "fCacheConnect"
-- 0x04 "fTranBoundary"
-- 0x02 client is an ODBC driver
-- 0x01 change to initial language must succeed
length = 0,
version = 0x71000001, -- Version 7.1
size = 0,
cli_version = 7, -- From jTDS JDBC driver
cli_pid = 0, -- Dummy value
conn_id = 0,
options_1 = 0xa0,
options_2 = 0x03,
sqltype_flag = 0,
reserved_flag= 0,
time_zone = 0,
collation = 0,
-- Strings
client = "Nmap",
username = nil,
password = nil,
app = "Nmap NSE",
server = nil,
library = "mssql.lua",
locale = "",
database = "master", --nil,
MAC = string.char(0x00,0x00,0x00,0x00,0x00,0x00), -- should contain client MAC, jTDS uses all zeroes
new = function(self,o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end,
--- Sets the username used for authentication
--
-- @param username string containing the username to user for authentication
SetUsername = function(self, username)
self.username = username
end,
--- Sets the password used for authentication
--
-- @param password string containing the password to user for authentication
SetPassword = function(self, password)
self.password = password
end,
--- Sets the database used in authentication
--
-- @param database string containing the database name
SetDatabase = function(self, database)
self.database = database
end,
--- Sets the server's name used in authentication
--
-- @param server string containing the name or ip of the server
SetServer = function(self, server)
self.server = server
end,
--- Returns the authentication packet as string
--
-- @return string containing the authentication packet
ToString = function(self)
local data
local offset = 86
self.cli_pid = math.random(100000)
self.length = offset + 2 * ( self.client:len() + self.username:len() + self.password:len() +
self.app:len() + self.server:len() + self.library:len() + self.database:len() )
data = bin.pack("<IIIIII", self.length, self.version, self.size, self.cli_version, self.cli_pid, self.conn_id )
data = data .. bin.pack("CCCC", self.options_1, self.options_2, self.sqltype_flag, self.reserved_flag )
data = data .. bin.pack("<II", self.time_zone, self.collation )
-- offsets begin
data = data .. bin.pack("<SS", offset, self.client:len() )
offset = offset + self.client:len() * 2
data = data .. bin.pack("<SS", offset, self.username:len() )
offset = offset + self.username:len() * 2
data = data .. bin.pack("<SS", offset, self.password:len() )
offset = offset + self.password:len() * 2
data = data .. bin.pack("<SS", offset, self.app:len() )
offset = offset + self.app:len() * 2
data = data .. bin.pack("<SS", offset, self.server:len() )
offset = offset + self.server:len() * 2
-- unknown1 offset
data = data .. bin.pack("<SS", 0, 0 )
data = data .. bin.pack("<SS", offset, self.library:len() )
offset = offset + self.library:len() * 2
data = data .. bin.pack("<SS", offset, self.locale:len() )
offset = offset + self.locale:len() * 2
data = data .. bin.pack("<SS", offset, self.database:len() )
offset = offset + self.database:len() * 2
-- client MAC address, hardcoded to 00:00:00:00:00:00
data = data .. bin.pack("A", self.MAC)
-- offset to auth info
data = data .. bin.pack("<S", offset)
-- lenght of nt auth (should be 0 for sql auth)
data = data .. bin.pack("<S", 0)
-- next position (same as total packet length)
data = data .. bin.pack("<S", self.length)
-- zero pad
data = data .. bin.pack("<S", 0)
-- Auth info wide strings
data = data .. bin.pack("A", Util.ToWideChar(self.client) )
data = data .. bin.pack("A", Util.ToWideChar(self.username) )
data = data .. bin.pack("A", self.TDS7CryptPass(self.password) )
data = data .. bin.pack("A", Util.ToWideChar(self.app) )
data = data .. bin.pack("A", Util.ToWideChar(self.server) )
data = data .. bin.pack("A", Util.ToWideChar(self.library) )
data = data .. bin.pack("A", Util.ToWideChar(self.locale) )
data = data .. bin.pack("A", Util.ToWideChar(self.database) )
return PacketType.Login, data
end,
--- Encrypts a password using the TDS7 *ultra secure* XOR encryption
--
-- @param password string containing the password to encrypt
-- @return string containing the encrypted password
TDS7CryptPass = function(password)
local xormask = 0x5a5a
local result = ""
for i=1, password:len() do
local c = bit.bxor( string.byte( password:sub( i, i ) ), xormask )
local m1= bit.band( bit.rshift( c, 4 ), 0x0F0F )
local m2= bit.band( bit.lshift( c, 4 ), 0xF0F0 )
result = result .. bin.pack("s", bit.bor( m1, m2 ) )
end
return result
end,
}
-- Handles communication with SQL Server
TDSStream = {
packetno = 0,
new = function(self,o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end,
--- Establishes a connection to the SQL server
--
-- @param host table containing host information
-- @param port table containing port information
-- @return status true on success, false on failure
-- @return result containing error message on failure
Connect = function( self, host, port )
local status, result, lport, _
self.socket = nmap.new_socket()
-- Sometimes a Query can take a long time to respond, so we set
-- the timeout to 30 seconds. This shouldn't be a problem as the
-- library attempt to decode the protocol and avoid reading past
-- the end of the input buffer. So the only time the timeout is
-- triggered is when waiting for a response to a query.
self.socket:set_timeout( MSSQL_TIMEOUT * 1000 )
status, result = self.socket:connect(host.ip, port.number, port.protocol)
status, _, lport, _, _ = self.socket:get_info()
if ( status ) then
math.randomseed(os.time() * lport )
else
math.randomseed(os.time() )
end
if ( not(status) ) then
return false, "Socket connection failed"
end
return status, result
end,
--- Disconnects from the SQL Server
--
-- @return status true on success, false on failure
-- @return result containing error message on failure
Disconnect = function( self )
local status, result = self.socket:close()
self.socket = nil
return status, result
end,
--- Sets the timeout for communication over the socket
--
-- @param timeout number containing the new socket timeout in ms
SetTimeout = function( self, timeout )
self.socket:set_timeout(timeout)
end,
--- Send a TDS request to the server
--
-- @param pkt_type number containing the type of packet to send
-- @param data string containing the raw data to send to the server
-- @return status true on success, false on failure
-- @return result containing error message on failure
Send = function( self, pkt_type, data )
local len = data:len() + 8
local last, channel, window = 1, 0, 0
local packet
self.packetno = self.packetno + 1
packet = bin.pack(">CCSSCCA", pkt_type, last, len, channel, self.packetno, window, data )
return self.socket:send( packet )
end,
--- Recieves responses from SQL Server
-- The function continues to read and assemble a response until the server
-- responds with the last response flag set
--
-- @return status true on success, false on failure
-- @return result containing raw data contents or error message on failure
Receive = function( self )
local status
local pkt_type, last, size, channel, packet_no, window, tmp, needed
local data, response = "", ""
local pos = 1
repeat
if( response:len() - pos < 4 ) then
status, tmp = self.socket:receive_bytes(4)
response = response .. tmp
end
if ( not(status) ) then
return false, "Failed to receive packet from MSSQL server"
end
pos, pkt_type, last, size = bin.unpack(">CCS", response, pos )
if ( pkt_type ~= PacketType.Response ) then
return false, "Server returned invalid packet"
end
needed = size - ( response:len() - pos + 5 )
if ( needed > 0 ) then
status, tmp = self.socket:receive_bytes(needed)
if ( not(status) ) then
return false, "Failed to receive packet from MSSQL server"
end
response = response .. tmp
end
pos, channel, packet_no, window, tmp = bin.unpack(">SccA" .. ( size - 8 ), response, pos)
data = data .. tmp
until last == 1
-- return only the data section ie. without the headers
return status, data
end,
}
--- Helper class
Helper =
{
new = function(self,o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end,
--- Establishes a connection to the SQL server
--
-- @param host table containing host information
-- @param port table containing port information
-- @return status true on success, false on failure
-- @return result containing error message on failure
Connect = function( self, host, port )
local status, result
self.stream = TDSStream:new()
status, result = self.stream:Connect(host, port)
if ( not(status) ) then
return false, result
end
return true
end,
--- Disconnects from the SQL Server
--
-- @return status true on success, false on failure
-- @return result containing error message on failure
Disconnect = function( self )
if ( not(self.stream) ) then
return false, "Not connected to server"
end
self.stream:Disconnect()
self.stream = nil
return true
end,
--- Authenticates to SQL Server
--
-- @param username string containing the username for authentication
-- @param password string containing the password for authentication
-- @param database string containing the database to access
-- @param servername string containing the name or ip of the remote server
-- @return status true on success, false on failure
-- @return result containing error message on failure
Login = function( self, username, password, database, servername )
local loginPacket = LoginPacket:new()
local status, result, data, token
local servername = servername or "DUMMY"
local pos = 1
if ( nil == self.stream ) then
return false, "Not connected to server"
end
loginPacket:SetUsername(username)
loginPacket:SetPassword(password)
loginPacket:SetDatabase(database)
loginPacket:SetServer(servername)
status, result = self.stream:Send( loginPacket:ToString() )
if ( not(status) ) then
return false, result
end
status, data = self.stream:Receive()
if ( not(status) ) then
return false, data
end
while( pos < data:len() ) do
pos, token = Token.ParseToken( data, pos )
if ( -1 == pos ) then
return false, token
end
-- Let's check for user must change password, it appears as if this is
-- reported as ERROR 18488
if ( token.type == TokenType.ErrorMessage and token.errno == 18488 ) then
return true, "Must change password at next logon"
elseif ( token.type == TokenType.LoginAcknowledgement ) then
return true, "Login Success"
end
end
return false, "Login Failed"
end,
--- Performs a SQL query and parses the response
--
-- @param query string containing the SQL query
-- @return status true on success, false on failure
-- @return table containing a table of columns for each row
-- or error message on failure
Query = function( self, query )
local queryPacket = QueryPacket:new()
local status, result, data, token, colinfo, rows
local pos = 1
if ( nil == self.stream ) then
return false, "Not connected to server"
end
queryPacket:SetQuery( query )
status, result = self.stream:Send( queryPacket:ToString() )
if ( not(status) ) then
return false, result
end
status, data = self.stream:Receive()
if ( not(status) ) then
return false, data
end
-- Iterate over tokens until we get to a rowtag
while( pos < data:len() ) do
local rowtag = select(2, bin.unpack("C", data, pos))
if ( rowtag == TokenType.Row ) then
break
end
pos, token = Token.ParseToken( data, pos )
if ( -1 == pos ) then
return false, token
end
if ( token.type == TokenType.ErrorMessage ) then
return false, token.error
elseif ( token.type == TokenType.TDS7Results ) then
colinfo = token.colinfo
end
end
rows = {}
while(true) do
local rowtag
pos, rowtag = bin.unpack("C", data, pos )
if ( rowtag ~= TokenType.Row ) then
break
end
if ( rowtag == TokenType.Row and colinfo and #colinfo > 0 ) then
local columns = {}
for i=1, #colinfo do
local val
if ( ColumnData.Parse[colinfo[i].type] ) then
pos, val = ColumnData.Parse[colinfo[i].type](data, pos)
if ( -1 == pos ) then
return false, val
end
table.insert(columns, val)
else
return false, ("unknown datatype=0x%X"):format(colinfo[i].type)
end
end
table.insert(rows, columns)
end
end
result = {}
result.rows = rows
result.colinfo = colinfo
return true, result
end,
}
--- "static" Utility class containing mostly conversion functions
Util =
{
--- Converts a string to a wide string
--
-- @param str string to be converted
-- @return string containing a two byte representation of str where a zero
-- byte character has been tagged on to each character.
ToWideChar = function( str )
return str:gsub("(.)", "%1" .. string.char(0x00) )
end,
--- Concerts a wide string to string
--
-- @param wstr containing the wide string to convert
-- @return string with every other character removed
FromWideChar = function( wstr )
local str = ""
if ( nil == wstr ) then
return nil
end
for i=1, wstr:len(), 2 do
str = str .. wstr:sub(i, i)
end
return str
end,
--- Takes a table as returned by Query and does some fancy formatting
-- better suitable for <code>stdnse.output_result</code>
--
-- @param tbl as recieved by <code>Helper.Query</code>
-- @param with_headers boolean true if output should contain column headers
-- @return table suitable for <code>stdnse.output_result</code>
FormatOutputTable = function ( tbl, with_headers )
local new_tbl = {}
local col_names = {}
if ( not(tbl) ) then
return
end
if ( with_headers and tbl.rows and #tbl.rows > 0 ) then
local headers
table.foreach( tbl.colinfo, function( k, v ) table.insert( col_names, v.text) end)
headers = stdnse.strjoin("\t", col_names)
table.insert( new_tbl, headers)
headers = headers:gsub("[^%s]", "=")
table.insert( new_tbl, headers )
end
for _, v in ipairs( tbl.rows ) do
table.insert( new_tbl, stdnse.strjoin("\t", v) )
end
return new_tbl
end,
}