---
-- MSSQL Library supporting a very limited subset of operations.
--
-- 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:
-- * SSRP Protocol Specification: http://msdn.microsoft.com/en-us/library/cc219703.aspx
-- * TDS Protocol Specification: http://msdn.microsoft.com/en-us/library/dd304523.aspx
-- * TDS Protocol Documentation: http://www.freetds.org/tds.html.
-- * The JTDS source code: http://jtds.sourceforge.net/index.html.
--
-- * ColumnInfo: 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.
-- * ColumnData: Class containing parsers for the actual column information.
-- * 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.
-- * QueryPacket: Class used to hold a query and convert it to a string suitable for transmission over a socket.
-- * LoginPacket: Class used to hold login specific data which can easily be converted to a string suitable for transmission over a socket.
-- * 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.
-- * Helper: Class which facilitates the use of the library by through action oriented functions with descriptive names.
-- * Util: A "static" class containing mostly character and type conversion functions.
--
-- 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:
-- * 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. 
-- * Version 7 and ONLY version 7 of the protocol is supported. This should cover Microsoft SQL Server 7.0 and later.
-- * TDS Responses contain one or more response tokens which are parsed based on their type. The supported tokens are listed in the <code>TokenType</code> table and their respective parsers can be found in the <code>Token</code> 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.
-- * 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 <code>DataTypes</code> table. In order to add additional data types a parser function has to be added to both the <code>ColumnInfo</code> and <code>ColumnData</code> class.
-- * No functionality for languages, localization or characted codepages has been considered or implemented.
-- * The library does database authentication only. No OS authentication or use of the integrated security model is supported.
-- * Queries using SELECT, INSERT, DELETE and EXEC of procedures have been tested while developing scripts.
--
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
--
-- @author "Patrik Karlsson <patrik@cqure.net>"
-- 
-- @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>.

module(... or "mssql", package.seeall)

-- 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
-- Revised 01/23/2011 - v0.3 - fixed parsing error in discovery code with patch
--							   from Chris Woodbury

require("bit")
require("bin")
require("stdnse")
require("nsedebug")

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,
	PreLogin = 0x12,
}

-- TDS response token types
TokenType = 
{
	TDS7Results = 0x81,
	ErrorMessage = 0xAA,
	InformationMessage = 0xAB,
	LoginAcknowledgement = 0xAD,
	Row = 0xD1,
	OrderBy = 0xA9,
	EnvironmentChange = 0xE3,
	Done = 0xFD,
	DoneInProc = 0xFF,
}

-- TDS pre-login option types
PreLoginOptionType = 
{
	Version = 0x00,
	Encryption = 0x01,
	InstOpt = 0x02,
	ThreadId = 0x03,
	MARS = 0x04,
	Terminator = 0xFF,
}

-- Lengths for the values of TDS pre-login option fields
PreLoginOptionLength_Client = 
{
	[PreLoginOptionType.Version] = 6,
	[PreLoginOptionType.Encryption] = 1,
	[PreLoginOptionType.InstOpt] = -1,
	[PreLoginOptionType.ThreadId] = 4,
	[PreLoginOptionType.MARS] = 1,
	[PreLoginOptionType.Terminator] = 0,
}

-- Lengths for the values of TDS pre-login option fields
PreLoginOptionLength_Server = 
{
	[PreLoginOptionType.Version] = 6,
	[PreLoginOptionType.Encryption] = 1,
	[PreLoginOptionType.InstOpt] = -1,
	[PreLoginOptionType.ThreadId] = -1, -- According to the TDS spec, this value "should" be empty from the server
	[PreLoginOptionType.MARS] = 1,
	[PreLoginOptionType.Terminator] = 0,
}

-- 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,
	
}


--- PreLoginPacket class
PreLoginPacket = 
{
	versionInfo = nil,
	__requestEncryption = 0,
	__instanceName = "",
	__threadId = 0, -- Dummy value; will be filled in later
	__requestMars = nil,

	new = function(self,o)
		o = o or {}
        setmetatable(o, self)
        self.__index = self
		return o
    end,
	
	--- Sets the client version (default = 9.00.00.00)
	--
	-- @param versionInfo version table with the client version information
	SetVersion = function(self, versionInfo)
		self.__versionInfo = versionInfo
	end,
	
	--- Sets whether to request encryption (default = false)
	--
	-- @param requestEncryption boolean indicating whether encryption will be requested
	SetRequestEncryption = function(self, requestEncryption)
		if requestEncryption then
			self.__requestEncryption = 1
		else
			self.__requestEncryption = 0
		end
	end,
	
	--- Sets whether to request MARS support (default = undefined)
	--
	-- @param requestMars boolean indicating whether MARS support will be requested
	SetRequestMars = function(self, requestMars)
		if requestMars then
			self.__requestMars = 1
		else
			self.__requestMars = 0
		end
	end,

	--- Sets the instance name of the target
	--
	-- @param instanceName string containing the name of the instance	
	SetInstanceName = function(self, instanceName)
		self.__instanceName = instanceName or ""
	end,
	
	--- Returns the pre-login packet as a byte string
	--
	-- @return byte string containing the pre-login packet
	ToBytes = function(self)
		local data, optionLength
		local offset = 1 -- Terminator
		offset = offset + 5 -- Version
		offset = offset + 5 -- Encryption
		offset = offset + 5 -- InstOpt
		offset = offset + 5 -- ThreadId
		if self.__requestMars then offset = offset + 3 end -- MARS
		
		math.randomseed(os.time())
		self.__threadId = math.random(1000)
		
		if not self.versionInfo then
			self.versionInfo = SqlServerVersionInfo:new()
			self.versionInfo:SetVersionNumber( "9.00.1399.00" )
		end
		
		optionLength = PreLoginOptionLength_Client[ PreLoginOptionType.Version ]
		data = bin.pack( ">CSS", PreLoginOptionType.Version, offset, optionLength )
		offset = offset + optionLength
		
		optionLength = PreLoginOptionLength_Client[ PreLoginOptionType.Encryption ]
		data = data .. bin.pack( ">CSS", PreLoginOptionType.Encryption, offset, optionLength )
		offset = offset + optionLength
		
		optionLength = #self.__instanceName + 1 --(string length + null-terminator)
		data = data .. bin.pack( ">CSS", PreLoginOptionType.InstOpt, offset, optionLength )
		offset = offset + optionLength
		
		optionLength = PreLoginOptionLength_Client[ PreLoginOptionType.ThreadId ]
		data = data .. bin.pack( ">CSS", PreLoginOptionType.ThreadId, offset, optionLength )
		offset = offset + optionLength
		
		if self.requestMars then
			optionLength = PreLoginOptionLength_Client[ PreLoginOptionType.MARS ]
			data = data .. bin.pack( ">CSS", PreLoginOptionType.MARS, offset, optionLength )
			offset = offset + optionLength
		end
		
		data = data .. bin.pack( "C", PreLoginOptionType.Terminator )
		
		-- Now that the pre-login headers are done, write the data
		data = data .. bin.pack( ">CCSS", self.versionInfo.major, self.versionInfo.minor,
					self.versionInfo.build, self.versionInfo.subBuild )
		data = data .. bin.pack( "C", self.__requestEncryption )
		data = data .. bin.pack( "z", self.__instanceName )
		data = data .. bin.pack( "<I", self.__threadId )
		if self.requestMars then
			data = data .. bin.pack( "C", self.__requestMars )
		end
		
		return PacketType.PreLogin, data
	end,
	
	--- 
	FromBytes = function( bytes )
		local status, pos = false, 1
		local preLoginPacket = PreLoginPacket:new()
		
		while true do
		
			local optionType, optionPos, optionLength, optionData, expectedOptionLength, _
			pos, optionType = bin.unpack("C", bytes, pos)
			if ( optionType == PreLoginOptionType.Terminator ) then
				status = true
				break
			end
			expectedOptionLength = PreLoginOptionLength_Server[ optionType ]
			if ( not expectedOptionLength ) then
				stdnse.print_debug( 2, "%s: Unrecognized pre-login option type: %s", "mssql.lua", optionType )
				expectedOptionLength = -1
			end
			
			pos, optionPos, optionLength = bin.unpack(">SS", bytes, pos)
			if not (optionPos and optionLength) then
				stdnse.print_debug( 2, "%s: Could not unpack optionPos and optionLength.", "mssql.lua" )
				return false, "Invalid pre-login response"
			end
			
			optionPos = optionPos + 1 -- convert from 0-based index to 1-based index
			if ( (optionPos + optionLength) > (#bytes + 1) ) then
				stdnse.print_debug( 2, "%s: Pre-login response: pos+len for option type %s is beyond end of data.", "mssql.lua", optionType )
				stdnse.print_debug( 2, "%s:   (optionPos: %s) (optionLength: %s)", "mssql.lua", optionPos, optionLength )
				return false, "Invalid pre-login response"
			end
			
			
			if ( optionLength ~= expectedOptionLength and expectedOptionLength ~= -1 ) then
				stdnse.print_debug( 2, "%s: Option data is incorrect size in pre-login response. ", "mssql.lua" )
				stdnse.print_debug( 2, "%s:   (optionType: %s) (optionLength: %s)", "mssql.lua", optionType, optionLength )
				return false, "Invalid pre-login response"
			end
			optionData = string.sub( bytes, optionPos, optionPos + optionLength - 1 )
			if #optionData ~= optionLength then
				stdnse.print_debug( 2, "%s: Could not read sufficient bytes from version data.", "mssql.lua" )
				return false, "Invalid pre-login response"
			end

			if ( optionType == PreLoginOptionType.Version ) then
				local major, minor, build, subBuild, version
				major = string.byte( optionData:sub( 1, 1 ) )
				minor = string.byte( optionData:sub( 2, 2 ) )
				build = (string.byte( optionData:sub( 3, 3 ) ) * 256) + string.byte( optionData:sub( 4, 4 ) )
				subBuild = (string.byte( optionData:sub( 5, 5 ) ) * 256) + string.byte( optionData:sub( 6, 6 ) )
				
				version = SqlServerVersionInfo:new()
				version:SetVersion( major, minor, build, subBuild, "SSNetLib" )
				preLoginPacket.versionInfo = version
			elseif ( optionType == PreLoginOptionType.Encryption ) then
				preLoginPacket:SetRequestEncryption( bin.unpack( "C", optionData ) )
			elseif ( optionType == PreLoginOptionType.InstOpt ) then
				preLoginPacket:SetInstanceName( bin.unpack( "z", optionData ) )
			elseif ( optionType == PreLoginOptionType.ThreadId ) then
				-- Do nothing. According to the TDS spec, this option is empty when sent from the server
			elseif ( optionType == PreLoginOptionType.MARS ) then
				preLoginPacket:SetRequestMars( bin.unpack( "C", optionData ) )
			end
		end
		
		return status, preLoginPacket
	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
		
		-- Offset to unused placeholder (reserved for future use in TDS spec)
		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()

		-- Set the timeout to something realistic for connects
		self.socket:set_timeout( 5000 )
		status, result = self.socket:connect(host, port)
		if ( not(status) ) then return false, "Connect failed" end

		-- 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, _, 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,
	
}


--- SqlServerVersionInfo class
SqlServerVersionInfo = 
{
	versionNumber = "",		-- The full version string (e.g. "9.00.2047.00") 
	major = nil,			-- The major version (e.g. 9)
	minor = nil,			-- The minor version (e.g. 0)
	build = nil,			-- The build number (e.g. 2047)
	subBuild = nil,			-- The sub-build number (e.g. 0)
	productName = nil,		-- The prodcut name (e.g. "SQL Server 2005")
	brandedVersion = nil,	-- The branded version of the product (e.g. "2005")
	servicePackLevel = nil,	-- The service pack leve (e.g. "SP1")
	patched = nil,			-- Whether patches have been applied since SP installation (true/false/nil)
	source = nil,			-- The source of the version info (e.g. "SSRP", "SSNetLib")

	new = function(self,o)
		o = o or {}
        setmetatable(o, self)
        self.__index = self
		return o
    end,
	
	--- Sets the version using a version number string.
	--
	-- @param versionNumber a version number string (e.g. "9.00.1399.00")
	-- @param source a string indicating the source of the version info (e.g. "SSRP", "SSNetLib")
	SetVersionNumber = function(self, versionNumber, source)
		local major, minor, revision, subBuild
		if versionNumber:match( "^%d+%.%d+%.%d+.%d+" ) then
			major, minor, revision, subBuild = versionNumber:match( "^(%d+)%.(%d+)%.(%d+)" )
		elseif versionNumber:match( "^%d+%.%d+%.%d+" ) then
			major, minor, revision = versionNumber:match( "^(%d+)%.(%d+)%.(%d+)" )
		else
			stdnse.print_debug( 1, "%s: SetVersionNumber: versionNumber is not in correct format: %s", "mssql.lua", versionNumber or "nil" )
		end

		self:SetVersion( major, minor, revision, subBuild, source )
	end,
	
	--- Sets the version using the individual numeric components of the version
	--  number.
	--
	-- @param source a string indicating the source of the version info (e.g. "SSRP", "SSNetLib")
	SetVersion = function(self, major, minor, build, subBuild, source)
		self.source = source
		-- make sure our version numbers all end up as valid numbers
		self.major, self.minor, self.build, self.subBuild = 
			tonumber( major or 0 ), tonumber( minor or 0 ), tonumber( build or 0 ), tonumber( subBuild or 0 )
		
		self.versionNumber = string.format( "%u.%02u.%u.%02u", self.major, self.minor, self.build, self.subBuild )
		
		self:__ParseVersionInfo()
	end,
		
	--- Using the version number, determines the product version
	__InferProductVersion = function(self)
		
		local VERSION_LOOKUP_TABLE = {
			["^6%.0"] = "6.0", ["^6%.5"] = "6.5", ["^7%.0"] = "7.0", 
			["^8%.0"] = "2000",	["^9%.0"] = "2005",	["^10%.0"] = "2008",
			["^10%.50"] = "2008 R2", ["^11%.0"] = "2011",
		}
		
		local product = ""
	
		for m, v in pairs(VERSION_LOOKUP_TABLE) do
			if ( self.versionNumber:match(m) ) then
				product = v
				self.brandedVersion = product
				break
			end
		end
		
		self.productName = ("Microsoft SQL Server %s"):format(product)
		
	end,
	
	
	--- Returns a lookup table that maps revision numbers to service pack levels for
	--  the applicable SQL Server version (e.g. { {1600, "RTM"}, {2531, "SP1"} }).
	__GetSpLookupTable = function(self)
	
		-- Service pack lookup tables: 
		-- For instances where a revised service pack was released (e.g. 2000 SP3a), we will include the
		-- build number for the original SP and the build number for the revision. However, leaving it
		-- like this would make it appear that subsequent builds were a patched version of the revision
		-- (e.g. a patch applied to 2000 SP3 that increased the build number to 780 would get displayed
		-- as "SP3a+", when it was actually SP3+). To avoid this, we will include an additional fake build
		-- number that combines the two.
		local SP_LOOKUP_TABLE_6_5 = { {201, "RTM"}, {213, "SP1"}, {240, "SP2"}, {258, "SP3"}, {281, "SP4"},
			{415, "SP5"}, {416, "SP5a"}, {417, "SP5/SP5a"}, }
		
		local SP_LOOKUP_TABLE_7 = { {623, "RTM"}, {699, "SP1"}, {842, "SP2"}, {961, "SP3"}, {1063, "SP4"}, }
		
		local SP_LOOKUP_TABLE_2000 = { {194, "RTM"}, {384, "SP1"}, {532, "SP2"}, {534, "SP2"}, {760, "SP3"},
			{766, "SP3a"}, {767, "SP3/SP3a"}, {2039, "SP4"}, }
		
		local SP_LOOKUP_TABLE_2005 = { {1399, "RTM"}, {2047, "SP1"}, {3042, "SP2"}, {4035, "SP3"}, }
		
		local SP_LOOKUP_TABLE_2008 = { {1600, "RTM"}, {2531, "SP1"}, {4000, "SP2"}, }
		
		local SP_LOOKUP_TABLE_2008R2 = { {1660, "RTM"}, }
	
	
		if ( not self.brandedVersion ) then
			self:__InferProductVersion()
		end
		
		local spLookupTable
		if self.brandedVersion == "6.5" then spLookupTable = SP_LOOKUP_TABLE_6_5
			elseif self.brandedVersion == "7.0" then spLookupTable = SP_LOOKUP_TABLE_7
			elseif self.brandedVersion == "2000" then spLookupTable = SP_LOOKUP_TABLE_2000
			elseif self.brandedVersion == "2005" then spLookupTable = SP_LOOKUP_TABLE_2005
			elseif self.brandedVersion == "2008" then spLookupTable = SP_LOOKUP_TABLE_2008
			elseif self.brandedVersion == "2008 R2" then spLookupTable = SP_LOOKUP_TABLE_2008R2
		end
		
		return spLookupTable
	
	end,
	
	
	--- Processes version data to determine (if possible) the product version,
	--  service pack level and patch status.
	__ParseVersionInfo = function(self)
	
		local spLookupTable = self:__GetSpLookupTable()
		
		if spLookupTable then
			
			local spLookupItr = 0
			-- Loop through the service pack levels until we find one whose revision
			-- number is the same as or lower than our revision number.
			while spLookupItr < #spLookupTable do
				spLookupItr = spLookupItr + 1
				
				if (spLookupTable[ spLookupItr ][1] == self.build ) then
					spLookupItr = spLookupItr
					break
				elseif (spLookupTable[ spLookupItr ][1] > self.build ) then
					-- The target revision number is lower than the first release
					if spLookupItr == 1 then
						self.servicePackLevel = "Pre-RTM"
					else
						-- we went too far - it's the previous SP, but with patches applied
						spLookupItr = spLookupItr - 1
					end
					break
				end
			end
			
			-- Now that we've identified the proper service pack level:
			if self.servicePackLevel ~= "Pre-RTM" then
				self.servicePackLevel = spLookupTable[ spLookupItr ][2]
				
				if ( spLookupTable[ spLookupItr ][1] == self.build ) then
					self.patched = false
				else
					self.patched = true
				end
			end
			
			-- Clean up some of our inferences. If the source of our revision number
			-- was the SSRP (SQL Server Browser) response, we need to recognize its
			-- limitations:
			--  * Versions of SQL Server prior to 2005 are reported with the RTM build
			--    number, regardless of the actual version (e.g. SQL Server 2000 is
			--    always 8.00.194).
			--  * Versions of SQL Server starting with 2005 (and going through at least
			--    2008) do better but are still only reported with the build number as
			--    of the last service pack (e.g. SQL Server 2005 SP3 with patches is
			--    still reported as 9.00.4035.00). 
			if ( self.source == "SSRP" ) then
				self.patched = nil
				
				if ( self.major <= 8 ) then
					self.servicePackLevel = nil
				end
			end
		end
		
		return true
	end,
	
	---
	ToString = function(self)
		local friendlyVersion = strbuf.new()
		if self.productName then
			friendlyVersion:concatbuf( self.productName )
			if self.servicePackLevel then
				friendlyVersion:concatbuf( " " )
				friendlyVersion:concatbuf( self.servicePackLevel )
			end
			if self.patched then
				friendlyVersion:concatbuf( "+" )
			end
		end
		
		return friendlyVersion:dump()
	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,
	
	--- Sends a broadcast message to the SQL Browser Agent and parses the
	-- results. The response is returned as an array of tables representing
	-- each database instance. The tables have the following fields:
	-- <code>servername</code> - the server name
	-- <code>name</code> - the name of the instance
	-- <code>clustered</code> - is the server clustered?
	-- <code>version</code> - the db version, WILL MOST LIKELY BE INCORRECT
	-- <code>port</code> - the TCP port of the server
	-- <code>pipe</code> - the location of the listening named pipe
	-- <code>ip</code> - the IP of the server
	--
	-- @param host table as received by the script action function
	-- @param port table as received by the script action function
	-- @param broadcast boolean true if the discovery should be performed
	--        against the broadcast address or not.
	-- @return status boolean, true on success false on failure
	-- @return instances array of instance tables
	Discover = function( host, port, broadcast )
		local socket = nmap.new_socket("udp")
		local instances = {}
		
		-- set a reasonable timeout
		socket:set_timeout(5000)
		
		local status, err
		
		if ( not(broadcast) ) then
			status, err = socket:connect( host, port )
			if ( not(status) ) then	return false, err end
			status, err = socket:send("\002")
			if ( not(status) ) then	return status, err end
		else
			status, err = socket:sendto(host, port, "\002")			
		end
		
		local data
		
		repeat
			status, data = socket:receive()
			if ( not(status) ) then
				stdnse.print_debug( 2, "%s: Socket:receive() error: %s", "mssql.lua", data )
				socket:close()
				return status, data
			end

			-- strip of first 3 bytes as they contain thing we don't want
			data = data:sub(4)
			stdnse.print_debug( 2, "%s: SSRP String=%s", "mssql.lua", data )
			
			local _, ip
			status, _, _, ip, _ = socket:get_info()
			
			
			-- It would seem easier to just capture (.-;;) repeateadly, since
			-- each instance ends with ";;", but ";;" can also occur within the
			-- data, signifying an empty field (e.g. "...bv;;@COMPNAME;;tcp;1433;;...").
			-- So, instead, we'll split up the string ahead of time.
			-- See the SSRP specification for more details.
			local instanceStrings = {}
			
			local firstInstanceEnd, instanceString
			repeat
				firstInstanceEnd = data:find( ";ServerName;(.-);InstanceName;(.-);IsClustered;(.-);" )
				if firstInstanceEnd then
					instanceString = data:sub( 1, firstInstanceEnd )
					data = data:sub( firstInstanceEnd + 1 )
				else
					instanceString = data
				end
				
				table.insert( instanceStrings, instanceString )
			until (not firstInstanceEnd)
			stdnse.print_debug( 3, "%s: SSRP Substrings:\n%s", "mssql.lua", nsedebug.tostr( instanceStrings) )
			
			for _, instance in ipairs( instanceStrings ) do
				instances[ip] = instances[ip] or {}

		  		local info = {}
		  		info.servername = string.match(instance, "ServerName;(.-);")
		  		info.name = string.match(instance, "InstanceName;(.-);")
		  		info.clustered = string.match(instance, "IsClustered;(.-);")
		  		info.version = string.match(instance, "Version;(.-);")
		  		info.port = tonumber( string.match(instance, ";tcp;(.-);") )
		  		info.pipe = string.match(instance, ";np;(.-);")
				info.ip = ip
				
				if ( not(instances[ip][info.name]) ) then
					instances[ip][info.name] = info
				end
			end
		until( not(broadcast) )
		socket:close()
		
		return true, instances
	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 false, "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,
	
	--- Attempts to connect to a SQL Server instance listening on a TCP port in
	--  order to determine the version of the SSNetLib DLL, which is an
	--  authoritative version number for the SQL Server instance itself.
	--
	-- @param host the hostname or IP address of the target instance (required)
	-- @param tcpPort the TCP port on which the instance is listening (required)
	-- @param instanceName the name of the SQL Server instance (optional, but
	--			omitting it may result in failure to get the version info)
	-- @return status true on success, false on failure
	-- @return versionInfo an instance of mssql.SqlServerVersionInfo, or nil
	GetInstanceVersion = function( host, tcpPort, instanceName )
			
		local status, response, version
		local tdsStream = TDSStream:new()
		
		status, response = tdsStream:Connect( host, tcpPort )
		if ( not(status) ) then
			stdnse.print_debug( 2, "%s: Connection to %s:%s failed: %s",
				"mssql.lua", host or "nil", tcpPort or "nil", response or "" )
			return false, "Connect failed"
		end
		
		local preLoginRequest = PreLoginPacket:new()
		preLoginRequest:SetInstanceName( instanceName )
		
		tdsStream:SetTimeout( 5000 )
		tdsStream:Send( preLoginRequest:ToBytes() )
		
		-- read in any response we might get
		status, response = tdsStream:Receive()
		tdsStream:Disconnect()
		
		if status then
			local preLoginResponse
			status, preLoginResponse = PreLoginPacket.FromBytes( response )
			if status then
				version = preLoginResponse.versionInfo
			else
				stdnse.print_debug( 2, "%s: Parsing of pre-login packet from %s:%s failed: %s",
					"mssql.lua", host or "nil", tcpPort or "nil", preLoginResponse or "" )
				return false, "Parsing failed"
			end
		else
			stdnse.print_debug( 2, "%s: Receive for %s:%s failed: %s",
				"mssql.lua", host or "nil", tcpPort or "nil", response or "" )
			return false, "Receive failed"
		end
		
		return status, version
	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,
	
	--- Decodes the version based on information from the SQL browser service.
	--
	-- @param info table with instance information as received by
	--        <code>Helper.Discover</code>
	-- @return status true on successm false on failure
	-- @return version table containing the following fields
	--         <code>product</code>, <code>version</code>,
	--         <code>level</code>
	DecodeBrowserInfoVersion = function(info)

		local VER_INFO = {
			["^6%.0"] = "6.0", ["^6%.5"] = "6.5", ["^7%.0"] = "7.0", 
			["^8%.0"] = "2000",	["^9%.0"] = "2005",	["^10%.0"]= "2008",
		}

		local VER_LEVEL = {
			["9.00.3042"] = "SP2", ["9.00.3043"] = "SP2", ["9.00.2047"] = "SP1",
			["9.00.1399"] = "RTM", ["10.0.1075"] = "CTP", ["10.0.1600"] = "CTP",
			["10.0.2531"] = "SP1"
		}

		local product = ""
		local version = {}

		for m, v in pairs(VER_INFO) do
			if ( info.version:match(m) ) then
				product=v
				break
			end
		end
		if ( info.name == "SQLEXPRESS" ) then
			product = product .. " Express Edition"
		end
		version.product = ("Microsoft SQL Server %s"):format(product)
		version.version = info.version
		for ver, level in pairs( VER_LEVEL ) do
			-- make sure we're comparing the same length
			local len = ( #info.version > #ver ) and #ver or #info.version
			if ( ver == info.version:sub(1, len) ) then
				version.level = level
				break
			end
		end
		if ( version.level ) then
			version.version = version.version .. (" (%s)"):format(version.level)
		end
		version.version = version.version .. " - UNVERIFIED"
		return true, version
	end
	
}
