local math = require "math"
local match = require "match"
local nmap = require "nmap"
local os = require "os"
local shortport = require "shortport"
local smb = require "smb"
local smbauth = require "smbauth"
local stdnse = require "stdnse"
local strbuf = require "strbuf"
local string = require "string"
local table = require "table"
local unicode = require "unicode"
_ENV = stdnse.module("mssql", stdnse.seeall)
local HAVE_SSL, openssl = pcall(require, "openssl")
do
  namedpipes = smb.namedpipes
  local arg = stdnse.get_script_args( "mssql.timeout" ) or "30s"
  local timeout, err = stdnse.parse_timespec(arg)
  if not timeout then
    error(err)
  end
  MSSQL_TIMEOUT = timeout
  SCANNED_PORTS_ONLY = false
  if ( stdnse.get_script_args( "mssql.scanned-ports-only" ) ) then
    SCANNED_PORTS_ONLY = true
  end
end
SqlServerInstanceInfo =
{
  instanceName = nil,
  version = nil,
  serverName = nil,
  isClustered = nil,
  host = nil,
  port = nil,
  pipeName = nil,
  new = function(self,o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
  end,
  __eq = function( self, other )
    local areEqual
    if ( not (self.host and other.host) ) then
      areEqual = false
    else
      areEqual = (self.host.ip == other.host.ip)
    end
    if (self.port and other.port) then
      areEqual = areEqual and ( other.port.number == self.port.number and
        other.port.protocol == self.port.protocol )
    elseif (self.pipeName and other.pipeName) then
      areEqual = areEqual and (self.pipeName == other.pipeName)
    elseif (self.instanceName and other.instanceName) then
      areEqual = areEqual and (self.instanceName == other.instanceName)
    else
      areEqual = false
    end
    return areEqual
  end,
  Merge = function( self, other, overwrite )
    local mergeFields = { "host", "port", "instanceName", "version", "isClustered", "pipeName" }
    for _, fieldname in ipairs( mergeFields ) do
      if ( other[ fieldname ] ~= nil and (overwrite or self[ fieldname ] == nil) ) then
        self[ fieldname ] = other[ fieldname ]
      end
    end
    if (self.version and self.version.source == "SSRP" and
        other.version and other.version.Source == "SSNetLib") then
      self.version = other.version
    end
  end,
  GetName = function( self )
    if (self.instanceName) then
      return string.format( "%s\\%s", self.host.ip or self.serverName or "[nil]", self.instanceName or "[nil]" )
    elseif (self.pipeName) then
      return string.format( "%s", self.pipeName )
    else
      return string.format( "%s:%s",  self.host.ip or self.serverName or "[nil]", (self.port and self.port.number) or "[nil]" )
    end
  end,
  SetIsClustered = function( self, isClustered )
    self.isClustered = (isClustered == true) or (isClustered == "Yes")
  end,
  HasNetworkProtocols = function( self )
    return (self.pipeName ~= nil) or (self.port and self.port.number)
  end,
}
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 product name (e.g. "SQL Server 2005")
  brandedVersion = nil, -- The branded version of the product (e.g. "2005")
  servicePackLevel = nil, -- The service pack level (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,
  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.debug1("%s: SetVersionNumber: versionNumber is not in correct format: %s", "MSSQL", versionNumber or "nil" )
    end
    self:SetVersion( major, minor, revision, subBuild, source )
  end,
  SetVersion = function(self, major, minor, build, subBuild, source)
    self.source = source
    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,
  _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"] = "2012", ["^12%.0"] = "2014",
      ["^13%.0"] = "2016", ["^14%.0"] = "2017",
    }
    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,
  _GetSpLookupTable = function(self)
    local SP_LOOKUP_TABLE = {
      ["6.5"] = {
        {201, "RTM"},
        {213, "SP1"},
        {240, "SP2"},
        {258, "SP3"},
        {281, "SP4"},
        {415, "SP5"},
        {416, "SP5a"},
        {417, "SP5/SP5a"},
      },
      ["7.0"] = {
        {623, "RTM"},
        {699, "SP1"},
        {842, "SP2"},
        {961, "SP3"},
        {1063, "SP4"},
      },
      ["2000"] = {
        {194, "RTM"},
        {384, "SP1"},
        {532, "SP2"},
        {534, "SP2"},
        {760, "SP3"},
        {766, "SP3a"},
        {767, "SP3/SP3a"},
        {2039, "SP4"},
      },
      ["2005"] = {
        {1399, "RTM"},
        {2047, "SP1"},
        {3042, "SP2"},
        {4035, "SP3"},
        {5000, "SP4"},
      },
      ["2008"] = {
        {1600, "RTM"},
        {2531, "SP1"},
        {4000, "SP2"},
        {5500, "SP3"},
        {6000, "SP4"},
      },
      ["2008 R2"] = {
        {1600, "RTM"},
        {2500, "SP1"},
        {4000, "SP2"},
        {6000, "SP3"},
      },
      ["2012"] = {
        {2100, "RTM"},
        {3000, "SP1"},
        {5058, "SP2"},
        {6020, "SP3"},
        {7001, "SP4"},
      },
      ["2014"] = {
        {2000, "RTM"},
        {4100, "SP1"},
        {5000, "SP2"},
        {6024, "SP3"},
      },
      ["2016"] = {
        {1601, "RTM"},
        {4001, "SP1"},
        {5026, "SP2"},
      },
      ["2017"] = {
        {1000, "RTM"},
      },
    }
    if ( not self.brandedVersion ) then
      self:_InferProductVersion()
    end
    local spLookupTable = SP_LOOKUP_TABLE[self.brandedVersion]
    stdnse.debug1("brandedVersion: %s, #lookup: %d", self.brandedVersion, spLookupTable and #spLookupTable or 0)
    return spLookupTable
  end,
  _ParseVersionInfo = function(self)
    local spLookupTable = self:_GetSpLookupTable()
    if spLookupTable then
      local spLookupItr = 0
      while spLookupItr < #spLookupTable do
        spLookupItr = spLookupItr + 1
        if (spLookupTable[ spLookupItr ][1] == self.build ) then
          spLookupItr = spLookupItr
          break
        elseif (spLookupTable[ spLookupItr ][1] > self.build ) then
          if spLookupItr == 1 then
            self.servicePackLevel = "Pre-RTM"
          else
            spLookupItr = spLookupItr - 1
          end
          break
        end
      end
      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
      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,
  PopulateNmapPortVersion = function(self, port)
    port.service = "ms-sql-s"
    port.version = port.version or {}
    port.version.name = "ms-sql-s"
    port.version.product = self.productName
    local versionString = strbuf.new()
    if self.source ~= "SSRP" then
      versionString:concatbuf( self.versionNumber )
      if self.servicePackLevel then
        versionString:concatbuf( "; " )
        versionString:concatbuf( self.servicePackLevel )
      end
      if self.patched then
        versionString:concatbuf( "+" )
      end
      port.version.version = versionString:dump()
    end
    return port
  end,
}
SSRP =
{
  PORT = { number = 1434, protocol = "udp" },
  DEBUG_ID = "MSSQL-SSRP",
  MESSAGE_TYPE =
  {
    ClientBroadcast = 0x02,
    ClientUnicast = 0x03,
    ClientUnicastInstance = 0x04,
    ClientUnicastDAC = 0x0F,
    ServerResponse = 0x05,
  },
  _ParseSsrpString = function( host, ssrpString )
    local instanceStrings = {}
    local firstInstanceEnd, instanceString
    repeat
      firstInstanceEnd = ssrpString:find( ";ServerName;(.-);InstanceName;(.-);IsClustered;(.-);" )
      if firstInstanceEnd then
        instanceString = ssrpString:sub( 1, firstInstanceEnd )
        ssrpString = ssrpString:sub( firstInstanceEnd + 1 )
      else
        instanceString = ssrpString
      end
      table.insert( instanceStrings, instanceString )
    until (not firstInstanceEnd)
    stdnse.debug2("%s: SSRP Substrings:\n  %s", SSRP.DEBUG_ID, table.concat(instanceStrings , "\n  ") )
    local instances = {}
    for _, instanceString in ipairs( instanceStrings ) do
      local instance = SqlServerInstanceInfo:new()
      local version = SqlServerVersionInfo:new()
      instance.version = version
      instance.host = host
      instance.serverName = instanceString:match( "ServerName;(.-);")
      instance.instanceName = instanceString:match( "InstanceName;(.-);")
      instance:SetIsClustered( instanceString:match( "IsClustered;(.-);") )
      version:SetVersionNumber( instanceString:match( "Version;(.-);"), "SSRP" )
      local tcpPort = tonumber( instanceString:match( ";tcp;(.-);") )
      if tcpPort then instance.port = {number = tcpPort, protocol = "tcp"} end
      local pipeName = instanceString:match( ";np;(.-);")
      local status, pipeSubPath = namedpipes.get_pipe_subpath( pipeName )
      if status then
        pipeName = namedpipes.make_pipe_name( host.ip, pipeSubPath )
      elseif pipeName ~= nil then
        stdnse.debug1("%s: Invalid pipe name:\n%s", SSRP.DEBUG_ID, pipeName )
      end
      instance.pipeName = pipeName
      table.insert( instances, instance )
    end
    return instances
  end,
  _ProcessResponse = function( host, responseData )
    local instances
    local pos, messageType, dataLength = 1, nil, nil
    messageType, dataLength, pos = string.unpack("<BI2", responseData, 1)
    responseData = responseData:sub(4)
    stdnse.debug2("%s: SSRP Data: %s", SSRP.DEBUG_ID, responseData )
    if ( messageType ~= SSRP.MESSAGE_TYPE.ServerResponse or
        dataLength ~= responseData:len() ) then
      stdnse.debug2("%s: Invalid SSRP response. Type: 0x%02x, Length: %d, Actual length: %d",
        SSRP.DEBUG_ID, messageType, dataLength, responseData:len() )
    else
      instances = SSRP._ParseSsrpString( host, responseData )
    end
    return instances
  end,
  DiscoverInstances = function( host, port )
    port = port or SSRP.PORT
    if ( SCANNED_PORTS_ONLY and nmap.get_port_state( host, port ) == nil ) then
      stdnse.debug2("%s: Discovery disallowed: scanned-ports-only is set and port %d was not scanned", SSRP.DEBUG_ID, port.number )
      return false, "Discovery disallowed: scanned-ports-only"
    end
    local socket = nmap.new_socket("udp")
    socket:set_timeout(5000)
    if ( port.number ~= SSRP.PORT.number ) then
      stdnse.debug1("%s: DiscoverInstances() called with non-standard port (%d)", SSRP.DEBUG_ID, port.number )
    end
    local status, err = socket:connect( host, port )
    if ( not(status) ) then return false, err end
    status, err = socket:send( string.pack( "B", SSRP.MESSAGE_TYPE.ClientUnicast ) )
    if ( not(status) ) then return false, err end
    local responseData, instances_host
    status, responseData = socket:receive()
    if ( not(status) ) then return false, responseData
    else
      instances_host = SSRP._ProcessResponse( host, responseData )
    end
    socket:close()
    return status, instances_host
  end,
  DiscoverInstances_Broadcast = function( host, port )
    port = port or SSRP.PORT
    local socket = nmap.new_socket("udp")
    socket:set_timeout(5000)
    local instances_all = {}
    if ( port.number ~= SSRP.PORT.number ) then
      stdnse.debug1("%S: DiscoverInstances_Broadcast() called with non-standard port (%d)", SSRP.DEBUG_ID, port.number )
    end
    local status, err = socket:sendto(host, port, string.pack( "B", SSRP.MESSAGE_TYPE.ClientBroadcast ))
    if ( not(status) ) then return false, err end
    while ( status ) do
      local responseData
      status, responseData = socket:receive()
      if ( status ) then
        local remoteIp, _
        status, _, _, remoteIp, _ = socket:get_info()
        local instances_host = SSRP._ProcessResponse( {ip = remoteIp, name = ""}, responseData )
        instances_all[ remoteIp ] = instances_host
      end
    end
    socket:close()
    return true, instances_all
  end,
}
PacketType =
{
  Query = 0x01,
  Response = 0x04,
  Login = 0x10,
  NTAuthentication = 0x11,
  PreLogin = 0x12,
}
TokenType =
{
  ReturnStatus         = 0x79,
  TDS7Results          = 0x81,
  ErrorMessage         = 0xAA,
  InformationMessage   = 0xAB,
  LoginAcknowledgement = 0xAD,
  Row                  = 0xD1,
  OrderBy              = 0xA9,
  EnvironmentChange    = 0xE3,
  NTLMSSP_CHALLENGE    = 0xed,
  Done                 = 0xFD,
  DoneProc             = 0xFE,
  DoneInProc           = 0xFF,
}
DataTypes =
{
  SQLTEXT       = 0x23,
  GUIDTYPE      = 0x24,
  SYBINTN       = 0x26,
  SYBINT2       = 0x34,
  SYBINT4       = 0x38,
  SYBDATETIME   = 0x3D,
  NTEXTTYPE     = 0x63,
  BITNTYPE      = 0x68,
  DECIMALNTYPE  = 0x6A,
  NUMERICNTYPE  = 0x6C,
  FLTNTYPE      = 0x6D,
  MONEYNTYPE    = 0x6E,
  SYBDATETIMN   = 0x6F,
  XSYBVARBINARY = 0xA5,
  XSYBVARCHAR   = 0xA7,
  BIGBINARYTYPE = 0xAD,
  BIGCHARTYPE   = 0xAF,
  XSYBNVARCHAR  = 0xE7,
  SQLNCHAR      = 0xEF,
}
LoginErrorType =
{
  AccountLockedOut = 15113,
  NotAssociatedWithTrustedConnection = 18452, -- This probably means that the server is set for Windows authentication only
  InvalidUsernameOrPassword = 18456,
  PasswordChangeFailed_PasswordNotAllowed = 18463,
  PasswordChangeFailed_PasswordTooShort = 18464,
  PasswordChangeFailed_PasswordTooLong = 18465,
  PasswordChangeFailed_PasswordNotComplex = 18466,
  PasswordChangeFailed_PasswordFilter = 18467,
  PasswordChangeFailed_UnexpectedError = 18468,
  PasswordExpired = 18487,
  PasswordMustChange = 18488,
}
LoginErrorMessage = {}
for i, v in pairs(LoginErrorType) do
  LoginErrorMessage[v] = i
end
ColumnInfo =
{
  Parse =
  {
    [DataTypes.SQLTEXT] = function( data, pos )
      local colinfo = {}
      local tmp
      colinfo.unknown, colinfo.codepage, colinfo.flags, colinfo.charset, pos = string.unpack("<I4I2I2B", data, pos )
      colinfo.tablenamelen, pos = string.unpack("<i2", data, pos )
      colinfo.tablename, pos = string.unpack("c" .. (colinfo.tablenamelen * 2), data, pos)
      colinfo.msglen, pos = string.unpack("<B", data, pos )
      tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos)
      colinfo.text = unicode.utf16to8(tmp)
      return pos, colinfo
    end,
    [DataTypes.GUIDTYPE] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
    end,
    [DataTypes.SYBINTN] = function( data, pos )
      local colinfo = {}
      local tmp
      colinfo.unknown, colinfo.msglen, pos = string.unpack("<BB", data, pos)
      tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos )
      colinfo.text = unicode.utf16to8(tmp)
      return pos, colinfo
    end,
    [DataTypes.SYBINT2] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos)
    end,
    [DataTypes.SYBINT4] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos)
    end,
    [DataTypes.SYBDATETIME] = function( data, pos )
      local colinfo = {}
      local tmp
      colinfo.msglen, pos = string.unpack("B", data, pos)
      tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos )
      colinfo.text = unicode.utf16to8(tmp)
      return pos, colinfo
    end,
    [DataTypes.NTEXTTYPE] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.SQLTEXT](data, pos)
    end,
    [DataTypes.BITNTYPE] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
    end,
    [DataTypes.DECIMALNTYPE] = function( data, pos )
      local colinfo = {}
      local tmp
      colinfo.unknown, colinfo.precision, colinfo.scale, pos = string.unpack("<BBB", data, pos)
      colinfo.msglen, pos = string.unpack("<B",data,pos)
      tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos )
      colinfo.text = unicode.utf16to8(tmp)
      return pos, colinfo
    end,
    [DataTypes.NUMERICNTYPE] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.DECIMALNTYPE](data, pos)
    end,
    [DataTypes.FLTNTYPE] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
    end,
    [DataTypes.MONEYNTYPE] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
    end,
    [DataTypes.SYBDATETIMN] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
    end,
    [DataTypes.XSYBVARBINARY] = function( data, pos )
      local colinfo = {}
      local tmp
      colinfo.lts, colinfo.msglen, pos = string.unpack("<I2B", data, pos)
      tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos )
      colinfo.text = unicode.utf16to8(tmp)
      return pos, colinfo
    end,
    [DataTypes.XSYBVARCHAR] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
    end,
    [DataTypes.BIGBINARYTYPE] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.XSYBVARBINARY](data, pos)
    end,
    [DataTypes.BIGCHARTYPE] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
    end,
    [DataTypes.XSYBNVARCHAR] = function( data, pos )
      local colinfo = {}
      local tmp
      colinfo.lts, colinfo.codepage, colinfo.flags, colinfo.charset,
      colinfo.msglen, pos = string.unpack("<I2I2I2BB", data, pos )
      tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos)
      colinfo.text = unicode.utf16to8(tmp)
      return pos, colinfo
    end,
    [DataTypes.SQLNCHAR] = function( data, pos )
      return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
    end,
  }
}
ColumnData =
{
  Parse = {
    [DataTypes.SQLTEXT] = function( data, pos )
      local len, coldata
      len, pos = string.unpack( "<B", data, pos )
      if ( len == 0 ) then
        return pos, 'Null'
      end
      pos = pos + len
      pos = pos + 8
      coldata, pos = string.unpack( "<s4", data, pos )
      return pos, coldata
    end,
    [DataTypes.GUIDTYPE] = function( data, pos )
      local len, coldata, index, nextdata
      local hex = {}
      len, pos = string.unpack("B", data, pos)
      if ( len == 0 ) then
        return pos, 'Null'
      elseif ( len == 16 ) then
        local A, B, C, D, E, pos = string.unpack("<I4I2I2>c2c6", data, pos)
        coldata = ("%08x-%04x-%04x-%s-%s"):format(A, B, C, stdnse.tohex(D), stdnse.tohex(E))
      else
        stdnse.debug1("Unhandled length (%d) for GUIDTYPE", len)
        return pos + len, 'Unsupported Data'
      end
      return pos, coldata
    end,
    [DataTypes.SYBINTN] = function( data, pos )
      local len, num
      len, pos = string.unpack("B", data, pos)
      if ( len == 0 ) then
        return pos, 'Null'
      elseif ( len <= 16 ) then
        local v, pos = string.unpack("<i" .. len, data, pos)
        return pos, v
      else
        return -1, ("Unhandled length (%d) for SYBINTN"):format(len)
      end
      return -1, "Error"
    end,
    [DataTypes.SYBINT2] = function( data, pos )
      local num
      num, pos = string.unpack("<I2", data, pos)
      return pos, num
    end,
    [DataTypes.SYBINT4] = function( data, pos )
      local num
      num, pos = string.unpack("<I4", data, pos)
      return pos, num
    end,
    [DataTypes.SYBDATETIME] = function( data, pos )
      local hi, lo, result_seconds, result
      local tds_epoch, system_epoch, tds_offset_seconds
      hi, lo, pos = string.unpack("<i4I4", data, pos)
      tds_epoch = os.time( {year = 1900, month = 1, day = 1, hour = 00, min = 00, sec = 00, isdst = nil} )
      system_epoch       = os.time( os.date("*t", 0))
      tds_offset_seconds = os.difftime(tds_epoch,system_epoch)
      result_seconds = (hi*24*60*60) + (lo/300)
      result = os.date("!%b %d, %Y %H:%M:%S", tds_offset_seconds + result_seconds )
      return pos, result
    end,
    [DataTypes.NTEXTTYPE] = function( data, pos )
      local len, coldata
      len, pos = string.unpack( "<B", data, pos )
      if ( len == 0 ) then
        return pos, 'Null'
      end
      pos = pos + len
      pos = pos + 8
      coldata, pos = string.unpack( "<s4", data, pos )
      return pos, unicode.utf16to8(coldata)
    end,
    [DataTypes.BITNTYPE] = function( data, pos )
      return ColumnData.Parse[DataTypes.SYBINTN](data, pos)
    end,
    [DataTypes.DECIMALNTYPE] = function( precision, scale, data, pos )
      local len, sign, format_string, coldata
      len, pos = string.unpack("<B", data, pos)
      if ( len == 0 ) then
        return pos, 'Null'
      end
      sign, pos = string.unpack("<B", data, pos)
      len = len - 1
      if ( len > 0 and len <= 16 ) then
        coldata, pos = string.unpack("<I" .. len, data, pos)
      else
        stdnse.debug1("Unhandled length (%d) for DECIMALNTYPE", len)
        return pos + len, 'Unsupported Data'
      end
      if ( sign == 0 ) then
        coldata = coldata * -1
      end
      coldata = coldata * (10^-scale)
      format_string = string.format("%%.%if", scale)
      coldata = string.format(format_string,coldata)
      return pos, coldata
    end,
    [DataTypes.NUMERICNTYPE] = function( precision, scale, data, pos )
      return ColumnData.Parse[DataTypes.DECIMALNTYPE]( precision, scale, data, pos )
    end,
    [DataTypes.SYBDATETIME] = function( data, pos )
      local hi, lo, result_seconds, result
      local tds_epoch, system_epoch, tds_offset_seconds
      hi, lo, pos = string.unpack("<i4I4", data, pos)
      tds_epoch = os.time( {year = 1900, month = 1, day = 1, hour = 00, min = 00, sec = 00, isdst = nil} )
      system_epoch       = os.time( os.date("*t", 0))
      tds_offset_seconds = os.difftime(tds_epoch,system_epoch)
      result_seconds = (hi*24*60*60) + (lo/300)
      result = os.date("!%b %d, %Y %H:%M:%S", tds_offset_seconds + result_seconds )
      return pos, result
    end,
    [DataTypes.BITNTYPE] = function( data, pos )
      return ColumnData.Parse[DataTypes.SYBINTN](data, pos)
    end,
    [DataTypes.NTEXTTYPE] = function( data, pos )
      local len, coldata
      len, pos = string.unpack( "<B", data, pos )
      if ( len == 0 ) then
        return pos, 'Null'
      end
      pos = pos + len
      pos = pos + 8
      coldata, pos = string.unpack( "<s4", data, pos )
      return pos, unicode.utf16to8(coldata)
    end,
    [DataTypes.FLTNTYPE] = function( data, pos )
      local len, coldata
      len, pos = string.unpack("<B", data, pos)
      if ( len == 0 ) then
        return pos, 'Null'
      elseif ( len == 4 ) then
        coldata, pos = string.unpack("<f", data, pos)
      elseif ( len == 8 ) then
        coldata, pos = string.unpack("<d", data, pos)
      end
      return pos, coldata
    end,
    [DataTypes.MONEYNTYPE] = function( data, pos )
      local len, value, coldata, hi, lo
      len, pos = string.unpack("B", data, pos)
      if ( len == 0 ) then
        return pos, 'Null'
      elseif ( len == 4 ) then
        value, pos = string.unpack("<i4", data, pos)
      elseif ( len == 8 ) then
        hi, lo, pos = string.unpack("<I4I4", data, pos)
        value = ( hi * 0x100000000 ) + lo
      else
        return -1, ("Unhandled length (%d) for MONEYNTYPE"):format(len)
      end
      coldata = string.format("%.4f",value/10000)
      return pos, coldata
    end,
    [DataTypes.SYBDATETIMN] = function( data, pos )
      local len, coldata
      len, pos = string.unpack( "<B", data, pos )
      if ( len == 0 ) then
        return pos, 'Null'
      elseif ( len == 4 ) then
        local days, mins
        days, mins, pos = string.unpack("<I2I2", data, pos)
        local tds_epoch = os.time( {year = 1900, month = 1, day = 1, hour = 00, min = 00, sec = 00, isdst = nil} )
        local system_epoch = os.time( os.date("*t", 0))
        local tds_offset_seconds = os.difftime(tds_epoch,system_epoch)
        local result_seconds = (days*24*60*60) + (mins*60)
        coldata = os.date("!%b %d, %Y %H:%M:%S", tds_offset_seconds + result_seconds )
        return pos,coldata
      elseif ( len == 8 ) then
        return ColumnData.Parse[DataTypes.SYBDATETIME](data, pos)
      else
        return -1, ("Unhandled length (%d) for SYBDATETIMN"):format(len)
      end
    end,
    [DataTypes.XSYBVARBINARY] = function( data, pos )
      local len, coldata
      len, pos = string.unpack( "<I2", data, pos )
      if ( len == 65535 ) then
        return pos, 'Null'
      else
        coldata, pos = string.unpack( "c"..len, data, pos )
        return pos, "0x" .. stdnse.tohex(coldata)
      end
      return -1, "Error"
    end,
    [DataTypes.XSYBVARCHAR] = function( data, pos )
      local len, coldata
      len, pos = string.unpack( "<I2", data, pos )
      if ( len == 65535 ) then
        return pos, 'Null'
      end
      coldata, pos = string.unpack( "c"..len, data, pos )
      return pos, coldata
    end,
    [DataTypes.BIGBINARYTYPE] = function( data, pos )
      return ColumnData.Parse[DataTypes.XSYBVARBINARY](data, pos)
    end,
    [DataTypes.BIGCHARTYPE] = function( data, pos )
      return ColumnData.Parse[DataTypes.XSYBVARCHAR](data, pos)
    end,
    [DataTypes.XSYBNVARCHAR] = function( data, pos )
      local len, coldata
      len, pos = string.unpack( "<I2", data, pos )
      if ( len == 65535 ) then
        return pos, 'Null'
      end
      coldata, pos = string.unpack( "c"..len, data, pos )
      return pos, unicode.utf16to8(coldata)
    end,
    [DataTypes.SQLNCHAR] = function( data, pos )
      return ColumnData.Parse[DataTypes.XSYBNVARCHAR](data, pos)
    end,
  }
}
Token =
{
  Parse = {
    [TokenType.ErrorMessage] = function( data, pos )
      local token = {}
      local tmp
      token.type = TokenType.ErrorMessage
      token.size, token.errno, token.state, token.severity, token.errlen, pos = string.unpack( "<I2I4BBI2", data, pos )
      tmp, pos = string.unpack("c" .. (token.errlen * 2), data, pos )
      token.error = unicode.utf16to8(tmp)
      token.srvlen, pos = string.unpack("B", data, pos)
      tmp, pos = string.unpack("c" .. (token.srvlen * 2), data, pos )
      token.server = unicode.utf16to8(tmp)
      token.proclen, pos = string.unpack("B", data, pos)
      tmp, pos = string.unpack("c" .. (token.proclen * 2), data, pos )
      token.proc = unicode.utf16to8(tmp)
      token.lineno, pos = string.unpack("<I2", data, pos)
      return pos, token
    end,
    [TokenType.EnvironmentChange] = function( data, pos )
      local token = {}
      local tmp
      token.type = TokenType.EnvironmentChange
      token.size, pos = string.unpack("<I2", data, pos)
      return pos + token.size, token
    end,
    [TokenType.InformationMessage] = function( data, pos )
      local pos, token = Token.Parse[TokenType.ErrorMessage]( data, pos )
      token.type = TokenType.InformationMessage
      return pos, token
    end,
    [TokenType.LoginAcknowledgement] = function( data, pos )
      local token = {}
      local _
      token.type = TokenType.LoginAcknowledgement
      token.size, _, _, _, _, token.textlen, pos = string.unpack( "<I2BBBI2B", data, pos )
      token.text, pos = string.unpack("c" .. token.textlen * 2, data, pos)
      token.version, pos = string.unpack("<I4", data, pos )
      return pos, token
    end,
    [TokenType.Done] = function( data, pos )
      local token = {}
      token.type = TokenType.Done
      token.flags, token.operation, token.rowcount, pos = string.unpack( "<I2I2I4", data, pos )
      return pos, token
    end,
    [TokenType.DoneProc] = function( data, pos )
      local token
      pos, token = Token.Parse[TokenType.Done]( data, pos )
      token.type = TokenType.DoneProc
      return pos, token
    end,
    [TokenType.DoneInProc] = function( data, pos )
      local token
      pos, token = Token.Parse[TokenType.Done]( data, pos )
      token.type = TokenType.DoneInProc
      return pos, token
    end,
    [TokenType.ReturnStatus] = function( data, pos )
      local token = {}
      token.value, pos = string.unpack("<i4", data, pos)
      token.type = TokenType.ReturnStatus
      return pos, token
    end,
    [TokenType.OrderBy] = function( data, pos )
      local token = {}
      token.size, pos = string.unpack("<I2", data, pos)
      token.type = TokenType.OrderBy
      return pos + token.size, token
    end,
    [TokenType.TDS7Results] = function( data, pos )
      local token = {}
      local _
      token.type = TokenType.TDS7Results
      token.count, pos = string.unpack( "<I2", data, pos )
      token.colinfo = {}
      for i=1, token.count do
        local colinfo = {}
        local usertype, flags, ttype
        usertype, flags, ttype, pos = string.unpack("<I2I2B", 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,
    [TokenType.NTLMSSP_CHALLENGE] = function(data, pos)
      local len, ntlmssp, msgtype, pos = string.unpack("<I2c8I4", data, pos)
      local NTLMSSP_CHALLENGE = 2
      if ( ntlmssp ~= "NTLMSSP\0" or msgtype ~= NTLMSSP_CHALLENGE ) then
        return -1, "Failed to process NTLMSSP Challenge"
      end
      local ntlm_challenge = data:sub( 28, 35 )
      pos = pos + len - 13
      return pos, ntlm_challenge
    end,
  },
  ParseToken = function( data, pos )
    local ttype
    ttype, pos = string.unpack("B", data, pos)
    if ( not(Token.Parse[ttype]) ) then
      stdnse.debug1("%s: No parser for token type 0x%X", "MSSQL", ttype )
      return -1, ("No parser for token type: 0x%X"):format( ttype )
    end
    return Token.Parse[ttype](data, pos)
  end,
}
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,
  ToString = function( self )
    return PacketType.Query, unicode.utf8to16( self.query )
  end,
}
PreLoginPacket =
{
  OPTION_TYPE = {
    Version = 0x00,
    Encryption = 0x01,
    InstOpt = 0x02,
    ThreadId = 0x03,
    MARS = 0x04,
    Terminator = 0xFF,
  },
  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,
  SetVersion = function(self, versionInfo)
    self._versionInfo = versionInfo
  end,
  SetRequestEncryption = function(self, requestEncryption)
    if requestEncryption then
      self._requestEncryption = 1
    else
      self._requestEncryption = 0
    end
  end,
  SetRequestMars = function(self, requestMars)
    if requestMars then
      self._requestMars = 1
    else
      self._requestMars = 0
    end
  end,
  SetInstanceName = function(self, instanceName)
    self._instanceName = instanceName or ""
  end,
  ToBytes = function(self)
    local OPTION_LENGTH_CLIENT = {
      [PreLoginPacket.OPTION_TYPE.Version] = 6,
      [PreLoginPacket.OPTION_TYPE.Encryption] = 1,
      [PreLoginPacket.OPTION_TYPE.InstOpt] = -1,
      [PreLoginPacket.OPTION_TYPE.ThreadId] = 4,
      [PreLoginPacket.OPTION_TYPE.MARS] = 1,
      [PreLoginPacket.OPTION_TYPE.Terminator] = 0,
    }
    local optionLength, optionType = 0, 0
    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
    if not self.versionInfo then
      self.versionInfo = SqlServerVersionInfo:new()
      self.versionInfo:SetVersionNumber( "9.00.1399.00" )
    end
    optionType = PreLoginPacket.OPTION_TYPE.Version
    optionLength = OPTION_LENGTH_CLIENT[ optionType ]
    local data = { string.pack( ">BI2I2", optionType, offset, optionLength ) }
    offset = offset + optionLength
    optionType = PreLoginPacket.OPTION_TYPE.Encryption
    optionLength = OPTION_LENGTH_CLIENT[ optionType ]
    data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength )
    offset = offset + optionLength
    optionType = PreLoginPacket.OPTION_TYPE.InstOpt
    optionLength = #self._instanceName + 1 --(string length + null-terminator)
    data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength )
    offset = offset + optionLength
    optionType = PreLoginPacket.OPTION_TYPE.ThreadId
    optionLength = OPTION_LENGTH_CLIENT[ optionType ]
    data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength )
    offset = offset + optionLength
    if self.requestMars then
      optionType = PreLoginPacket.OPTION_TYPE.MARS
      optionLength = OPTION_LENGTH_CLIENT[ optionType ]
      data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength )
      offset = offset + optionLength
    end
    data[#data+1] = string.pack( "B", PreLoginPacket.OPTION_TYPE.Terminator )
    data[#data+1] = string.pack( ">BBI2I2", self.versionInfo.major, self.versionInfo.minor,
    self.versionInfo.build, self.versionInfo.subBuild )
    data[#data+1] = string.pack( "<BzI4", self._requestEncryption, self._instanceName, self._threadId )
    if self.requestMars then
      data[#data+1] = string.pack( "B", self._requestMars )
    end
    return PacketType.PreLogin, table.concat(data)
  end,
  FromBytes = function( bytes )
    local OPTION_LENGTH_SERVER = {
      [PreLoginPacket.OPTION_TYPE.Version] = 6,
      [PreLoginPacket.OPTION_TYPE.Encryption] = 1,
      [PreLoginPacket.OPTION_TYPE.InstOpt] = -1,
      [PreLoginPacket.OPTION_TYPE.ThreadId] = 0, -- According to the TDS spec, this value should be empty from the server
      [PreLoginPacket.OPTION_TYPE.MARS] = 1,
      [PreLoginPacket.OPTION_TYPE.Terminator] = 0,
    }
    local status, pos = false, 1
    local preLoginPacket = PreLoginPacket:new()
    while true do
      local optionType, optionPos, optionLength, optionData, expectedOptionLength, _
      if pos > #bytes then
        stdnse.debug2("%s: Could not extract optionType.", "MSSQL" )
        return false, "Invalid pre-login response"
      end
      optionType, pos = ("B"):unpack(bytes, pos)
      if ( optionType == PreLoginPacket.OPTION_TYPE.Terminator ) then
        status = true
        break
      end
      expectedOptionLength = OPTION_LENGTH_SERVER[ optionType ]
      if ( not expectedOptionLength ) then
        stdnse.debug2("%s: Unrecognized pre-login option type: %s", "MSSQL", optionType )
        expectedOptionLength = -1
      end
      if pos + 4 > #bytes + 1 then
        stdnse.debug2("%s: Could not unpack optionPos and optionLength.", "MSSQL" )
        return false, "Invalid pre-login response"
      end
      optionPos, optionLength, pos = (">I2I2"):unpack(bytes, pos)
      optionPos = optionPos + 1 -- convert from 0-based index to 1-based index
      if ( optionLength ~= expectedOptionLength and expectedOptionLength ~= -1 ) then
        stdnse.debug2("%s: Option data is incorrect size in pre-login response. ", "MSSQL" )
        stdnse.debug2("%s:   (optionType: %s) (optionLength: %s)", "MSSQL", optionType, optionLength )
        return false, "Invalid pre-login response"
      end
      optionData = bytes:sub( optionPos, optionPos + optionLength - 1 )
      if #optionData ~= optionLength then
        stdnse.debug2("%s: Could not read sufficient bytes from version data.", "MSSQL" )
        return false, "Invalid pre-login response"
      end
      if ( optionType == PreLoginPacket.OPTION_TYPE.Version ) then
        local major, minor, build, subBuild = (">BBI2I2"):unpack(optionData)
        local version = SqlServerVersionInfo:new()
        version:SetVersion( major, minor, build, subBuild, "SSNetLib" )
        preLoginPacket.versionInfo = version
      elseif ( optionType == PreLoginPacket.OPTION_TYPE.Encryption ) then
        preLoginPacket:SetRequestEncryption( ("B"):unpack(optionData) )
      elseif ( optionType == PreLoginPacket.OPTION_TYPE.InstOpt ) then
        preLoginPacket:SetInstanceName( ("z"):unpack(optionData) )
      elseif ( optionType == PreLoginPacket.OPTION_TYPE.ThreadId ) then
      elseif ( optionType == PreLoginPacket.OPTION_TYPE.MARS ) then
        preLoginPacket:SetRequestMars( ("B"):unpack(optionData) )
      end
    end
    return status, preLoginPacket
  end,
}
LoginPacket =
{
  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,
  client = "Nmap",
  username = nil,
  password = nil,
  app = "Nmap NSE",
  server = nil,
  library = "mssql.lua",
  locale = "",
  database = "master", --nil,
  MAC = "\x00\x00\x00\x00\x00\x00", -- should contain client MAC, jTDS uses all zeroes
  new = function(self,o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
  end,
  SetUsername = function(self, username)
    self.username = username
  end,
  SetPassword = function(self, password)
    self.password = password
  end,
  SetDatabase = function(self, database)
    self.database = database
  end,
  SetServer = function(self, server)
    self.server = server
  end,
  SetDomain = function(self, domain)
    self.domain = domain
  end,
  ToString = function(self)
    local data
    local offset = 86
    local ntlmAuth = not(not(self.domain))
    local authLen = 0
    self.cli_pid = math.random(100000)
    self.length = offset + 2 * ( self.client:len() + self.app:len() + self.server:len() + self.library:len() + self.database:len() )
    if ( ntlmAuth ) then
      authLen = 32 + #self.domain
      self.length = self.length + authLen
      self.options_2 = self.options_2 + 0x80
    else
      self.length = self.length + 2 * (self.username:len() + self.password:len())
    end
    data = {
      string.pack("<I4I4I4I4I4I4", self.length, self.version, self.size, self.cli_version, self.cli_pid, self.conn_id ),
      string.pack("BBBB", self.options_1, self.options_2, self.sqltype_flag, self.reserved_flag ),
      string.pack("<I4I4", self.time_zone, self.collation ),
      string.pack("<I2I2", offset, self.client:len() ),
    }
    offset = offset + self.client:len() * 2
    if ( not(ntlmAuth) ) then
      data[#data+1] = string.pack("<I2I2", offset, self.username:len() )
      offset = offset + self.username:len() * 2
      data[#data+1] = string.pack("<I2I2", offset, self.password:len() )
      offset = offset + self.password:len() * 2
    else
      data[#data+1] = string.pack("<I2I2", offset, 0 )
      data[#data+1] = string.pack("<I2I2", offset, 0 )
    end
    data[#data+1] = string.pack("<I2I2", offset, self.app:len() )
    offset = offset + self.app:len() * 2
    data[#data+1] = string.pack("<I2I2", offset, self.server:len() )
    offset = offset + self.server:len() * 2
    data[#data+1] = string.pack("<I2I2", 0, 0 )
    data[#data+1] = string.pack("<I2I2", offset, self.library:len() )
    offset = offset + self.library:len() * 2
    data[#data+1] = string.pack("<I2I2", offset, self.locale:len() )
    offset = offset + self.locale:len() * 2
    data[#data+1] = string.pack("<I2I2", offset, self.database:len() )
    offset = offset + self.database:len() * 2
    data[#data+1] = self.MAC
    data[#data+1] = string.pack("<I2", offset)
    data[#data+1] = string.pack("<I2", authLen)
    data[#data+1] = string.pack("<I2", self.length)
    data[#data+1] = string.pack("<I2", 0)
    data[#data+1] = unicode.utf8to16(self.client)
    if ( not(ntlmAuth) ) then
      data[#data+1] = unicode.utf8to16(self.username)
      data[#data+1] = Auth.TDS7CryptPass(self.password)
    end
    data[#data+1] = unicode.utf8to16(self.app)
    data[#data+1] = unicode.utf8to16(self.server)
    data[#data+1] = unicode.utf8to16(self.library)
    data[#data+1] = unicode.utf8to16(self.locale)
    data[#data+1] = unicode.utf8to16(self.database)
    if ( ntlmAuth ) then
      local NTLMSSP_NEGOTIATE = 1
      local flags = 0x0000b201
      local workstation = ""
      data[#data+1] = "NTLMSSP\0"
      data[#data+1] = string.pack("<I4I4", NTLMSSP_NEGOTIATE, flags)
      data[#data+1] = string.pack("<I2I2I4", #self.domain, #self.domain, 32)
      data[#data+1] = string.pack("<I2I2I4", #workstation, #workstation, 32)
      data[#data+1] = self.domain:upper()
    end
    return PacketType.Login, table.concat(data)
  end,
}
NTAuthenticationPacket = {
  new = function(self, username, password, domain, nonce)
    local o = {}
    setmetatable(o, self)
    o.username = username
    o.domain = domain
    o.nonce = nonce
    o.password = password
    self.__index = self
    return o
  end,
  ToString = function(self)
    local ntlmssp = "NTLMSSP\0"
    local NTLMSSP_AUTH = 3
    local domain = unicode.utf8to16(self.domain:upper())
    local user = unicode.utf8to16(self.username)
    local hostname, sessionkey = "", ""
    local flags = 0x00008201
    local ntlm_response = Auth.NtlmResponse(self.password, self.nonce)
    local lm_response = Auth.LmResponse(self.password, self.nonce)
    local domain_offset = 64
    local username_offset = domain_offset + #domain
    local lm_response_offset = username_offset + #user
    local ntlm_response_offset = lm_response_offset + #lm_response
    local hostname_offset = ntlm_response_offset + #ntlm_response
    local sessionkey_offset = hostname_offset + #hostname
    local data = ntlmssp .. string.pack("<I4I2I2I4", NTLMSSP_AUTH, #lm_response, #lm_response, lm_response_offset)
    .. string.pack("<I2I2I4", #ntlm_response, #ntlm_response, ntlm_response_offset)
    .. string.pack("<I2I2I4", #domain, #domain, domain_offset)
    .. string.pack("<I2I2I4", #user, #user, username_offset)
    .. string.pack("<I2I2I4", #hostname, #hostname, hostname_offset)
    .. string.pack("<I2I2I4", #sessionkey, #sessionkey, sessionkey_offset)
    .. string.pack("<I4", flags)
    .. domain
    .. user
    .. lm_response .. ntlm_response
    return PacketType.NTAuthentication, data
  end,
}
TDSStream = {
  MESSAGE_STATUS_FLAGS = {
    Normal = 0x0,
    EndOfMessage = 0x1,
    IgnoreThisEvent = 0x2,
    ResetConnection = 0x4,
    ResetConnectionSkipTran = 0x8,
  },
  _packetId = 0,
  _pipe = nil,
  _socket = nil,
  _name = nil,
  new = function(self,o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
  end,
  ConnectEx = function( self, instanceInfo, connectionPreference, smbOverrides )
    if ( self._socket ) then return false, "Already connected via TCP" end
    if ( self._pipe ) then return false, "Already connected via named pipes" end
    connectionPreference = connectionPreference or stdnse.get_script_args('mssql.protocol') or { "TCP", "Named Pipes" }
    if ( connectionPreference and 'string' == type(connectionPreference) ) then
      connectionPreference = { connectionPreference }
    end
    local status, result, connectionType, errorMessage
    stdnse.debug3("%s: Connection preferences for %s: %s",
    "MSSQL", instanceInfo:GetName(), table.concat(connectionPreference, ", ") )
    for _, connectionType in ipairs( connectionPreference ) do
      if connectionType == "TCP" then
        if not ( instanceInfo.port ) then
          stdnse.debug3("%s: Cannot connect to %s via TCP because port table is not set.",
          "MSSQL", instanceInfo:GetName() )
          result = "No TCP port for this instance"
        else
          status, result = self:Connect( instanceInfo.host, instanceInfo.port )
          if status then return true end
        end
      elseif connectionType == "Named Pipes" or connectionType == "NP" then
        if not ( instanceInfo.pipeName ) then
          stdnse.debug3("%s: Cannot connect to %s via named pipes because pipe name is not set.",
          "MSSQL", instanceInfo:GetName() )
          result = "No named pipe for this instance"
        else
          status, result = self:ConnectToNamedPipe( instanceInfo.host, instanceInfo.pipeName, smbOverrides )
          if status then return true end
        end
      else
        stdnse.debug1("%s: Unknown connection preference: %s", "MSSQL", connectionType )
        return false, ("ERROR: Unknown connection preference: %s"):format(connectionType)
      end
      if not status then
        if errorMessage then
          errorMessage = string.format( "%s, %s: %s", errorMessage, connectionType, result or "nil" )
        else
          errorMessage = string.format( "%s: %s", connectionType, result or "nil" )
        end
      end
    end
    if not errorMessage then
      errorMessage = string.format( "%s: None of the preferred connection types are available for %s\\%s",
      "MSSQL", instanceInfo:GetName() )
    end
    return false, errorMessage
  end,
  ConnectToNamedPipe = function( self, host, pipePath, overrides )
    if ( self._socket ) then return false, "Already connected via TCP" end
    if ( SCANNED_PORTS_ONLY and smb.get_port( host ) == nil ) then
      stdnse.debug2("%s: Connection disallowed: scanned-ports-only is set and no SMB port is available", "MSSQL" )
      return false, "Connection disallowed: scanned-ports-only"
    end
    pipePath = pipePath or "\\sql\\query"
    self._pipe = namedpipes.named_pipe:new()
    local status, result = self._pipe:connect( host, pipePath, overrides )
    if ( status ) then
      self._name = self._pipe.pipe
    else
      self._pipe = nil
    end
    return status, result
  end,
  Connect = function( self, host, port )
    if ( self._pipe ) then return false, "Already connected via named pipes" end
    if ( SCANNED_PORTS_ONLY and nmap.get_port_state( host, port ) == nil ) then
      stdnse.debug2("%s: Connection disallowed: scanned-ports-only is set and port %d was not scanned", "MSSQL", port.number )
      return false, "Connection disallowed: scanned-ports-only"
    end
    local status, result, lport, _
    self._socket = nmap.new_socket()
    self._socket:set_timeout( 5000 )
    status, result = self._socket:connect(host, port)
    if ( status ) then
      self._socket:set_timeout( MSSQL_TIMEOUT * 1000 )
      status, _, lport, _, _ = self._socket:get_info()
    end
    if ( not(status) ) then
      self._socket = nil
      stdnse.debug2("%s: Socket connection failed on %s:%s", "MSSQL", host.ip, port.number )
      return false, "Socket connection failed"
    end
    self._name = string.format( "%s:%s", host.ip, port.number )
    return status, result
  end,
  Disconnect = function( self )
    if ( self._socket ) then
      local status, result = self._socket:close()
      self._socket = nil
      return status, result
    elseif ( self._pipe ) then
      local status, result = self._pipe:disconnect()
      self._pipe = nil
      return status, result
    else
      return false, "Not connected"
    end
  end,
  SetTimeout = function( self, timeout )
    if ( self._socket ) then
      self._socket:set_timeout(timeout)
    else
      return false, "Not connected"
    end
  end,
  GetNamedPipeName = function( self )
    if ( self._pipe ) then
      return self._pipe.name
    else
      return nil
    end
  end,
  Send = function( self, packetType, packetData )
    local packetLength = packetData:len() + 8 -- +8 for TDS header
    local messageStatus, spid, window = 1, 0, 0
    if ( packetType ~= PacketType.NTAuthentication ) then self._packetId = self._packetId + 1 end
    local assembledPacket = string.pack(">BBI2I2BB", packetType, messageStatus, packetLength, spid, self._packetId, window) .. packetData
    if ( self._socket ) then
      return self._socket:send( assembledPacket )
    elseif ( self._pipe ) then
      return self._pipe:send( assembledPacket )
    else
      return false, "Not connected"
    end
  end,
  Receive = function( self )
    local status, result, errorDetail
    local combinedData, readBuffer = "", "" -- the buffer is solely for the benefit of TCP connections
    local tdsPacketAvailable = true
    if not ( self._socket or self._pipe ) then
      return false, "Not connected"
    end
    while ( tdsPacketAvailable ) do
      local packetType, messageStatus, packetLength, spid, window
      local pos = 1
      if ( self._socket ) then
        if ( readBuffer:len() < 8 ) then
          status, result = self._socket:receive_bytes(8 - readBuffer:len())
          readBuffer = readBuffer .. result
        end
      elseif ( self._pipe ) then
        status, result, errorDetail = self._pipe:receive()
        readBuffer = result
      end
      if not ( status and readBuffer ) then return false, result, errorDetail end
      if ( readBuffer:len() < 8 ) then
        stdnse.debug2("%s: Receiving (%s): packet is invalid length", "MSSQL", self._name )
        return false, "Server returned invalid packet"
      end
      packetType, messageStatus, packetLength, pos = string.unpack(">BBI2", readBuffer, pos )
      spid, self._packetId, window, pos = string.unpack(">I2BB", readBuffer, pos )
      if ( packetType ~= PacketType.Response ) then
        stdnse.debug2("%s: Receiving (%s): Expected type 0x4 (response), but received type 0x%x",
          "MSSQL", self._name, packetType )
        return false, "Server returned invalid packet"
      end
      if ( self._socket ) then
        while ( packetLength - readBuffer:len() > 0 ) do
          status, result = self._socket:receive()
          if not ( status and result ) then return false, result end
          readBuffer = readBuffer .. result
        end
      end
      local thisPacketData = readBuffer:sub( pos, packetLength )
      combinedData = combinedData .. thisPacketData
      if ( self._socket ) then
        readBuffer = readBuffer:sub( packetLength + 1 )
      end
      if ( packetLength ~= (thisPacketData:len() + 8) ) then
        stdnse.debug2("%s: Receiving (%s): Header reports length %d, actual length is %d",
          "MSSQL", self._name, packetLength, thisPacketData:len()  )
        return false, "Server returned invalid packet"
      end
      tdsPacketAvailable = (( messageStatus & TDSStream.MESSAGE_STATUS_FLAGS.EndOfMessage) ~=
        TDSStream.MESSAGE_STATUS_FLAGS.EndOfMessage)
    end
    return status, combinedData
  end,
}
Helper =
{
  new = function(self,o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
  end,
  ConnectEx = function( self, instanceInfo )
    local status, result
    self.stream = TDSStream:new()
    status, result = self.stream:ConnectEx( instanceInfo )
    if ( not(status) ) then
      return false, result
    end
    return true
  end,
  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,
  WasDiscoveryPerformed = function( host )
    local mutex = nmap.mutex( "discovery_performed for " .. host.ip )
    mutex( "lock" )
    nmap.registry.mssql = nmap.registry.mssql or {}
    nmap.registry.mssql.discovery_performed = nmap.registry.mssql.discovery_performed or {}
    local wasPerformed = nmap.registry.mssql.discovery_performed[ host.ip ] or false
    mutex( "done" )
    return wasPerformed
  end,
  AddOrMergeInstance = function( newInstance )
    local instanceExists
    nmap.registry.mssql = nmap.registry.mssql or {}
    nmap.registry.mssql.instances = nmap.registry.mssql.instances or {}
    nmap.registry.mssql.instances[ newInstance.host.ip ] = nmap.registry.mssql.instances[ newInstance.host.ip ] or {}
    for _, existingInstance in ipairs( nmap.registry.mssql.instances[ newInstance.host.ip ] ) do
      if existingInstance == newInstance then
        existingInstance:Merge( newInstance )
        instanceExists = true
        break
      end
    end
    if not instanceExists then
      table.insert( nmap.registry.mssql.instances[ newInstance.host.ip ], newInstance )
    end
  end,
  GetDiscoveredInstances = function( host, port )
    nmap.registry.mssql = nmap.registry.mssql or {}
    nmap.registry.mssql.instances = nmap.registry.mssql.instances or {}
    nmap.registry.mssql.instances[ host.ip ] = nmap.registry.mssql.instances[ host.ip ] or {}
    if ( not port ) then
      local instances = nmap.registry.mssql.instances[ host.ip ]
      if ( instances and #instances == 0 ) then instances = nil end
      return instances
    else
      for _, instance in ipairs( nmap.registry.mssql.instances[ host.ip ] ) do
        if ( instance.port and instance.port.number == port.number and
          instance.port.protocol == port.protocol ) then
          return { instance }
        end
      end
      return nil
    end
  end,
  DiscoverBySsrp = function( host, port, broadcast )
    if broadcast then
      local status, result = SSRP.DiscoverInstances_Broadcast( host, port )
      if not status then
        return status, result
      else
        for ipAddress, host in pairs( result ) do
          for _, instance in ipairs( host ) do
            Helper.AddOrMergeInstance( instance )
            if ( instance.port and instance.version ) then
              instance.version:PopulateNmapPortVersion( instance.port )
            end
          end
        end
        return true, result
      end
    else
      local status, result = SSRP.DiscoverInstances( host, port )
      if not status then
        return status, result
      else
        for _, instance in ipairs( result ) do
          Helper.AddOrMergeInstance( instance )
          if ( instance.port and instance.version ) then
            instance.version:PopulateNmapPortVersion( instance.port )
            nmap.set_port_version( host, instance.port)
          end
        end
        local instances_all = {}
        instances_all[ host.ip ] = result
        return true, instances_all
      end
    end
  end,
  DiscoverByTcp = function( host, port )
    local version, instance, status
    instance = Helper.GetDiscoveredInstances( host, port )
    if ( not instance ) then
      instance =  SqlServerInstanceInfo:new()
      instance.host = host
      instance.port = port
      status, version = Helper.GetInstanceVersion( instance )
      if ( status ) then
        Helper.AddOrMergeInstance( instance )
        instance.version = version
        if ( instance.port and instance.version ) then
          instance.version:PopulateNmapPortVersion( instance.port )
          nmap.set_port_version( host, instance.port)
        end
      end
    end
    return (instance ~= nil), { instance }
  end,
  DiscoverBySmb = function( host, port )
    local defaultPipes = {
      "\\sql\\query",
      "\\MSSQL$SQLEXPRESS\\sql\\query",
      "\\MSSQL$SQLSERVER\\sql\\query",
    }
    local tdsStream = TDSStream:new()
    local status, result, instances_host
    for _, pipeSubPath in ipairs( defaultPipes ) do
      status, result = tdsStream:ConnectToNamedPipe( host, pipeSubPath, nil )
      if status then
        instances_host = {}
        local instance = SqlServerInstanceInfo:new()
        instance.pipeName = tdsStream:GetNamedPipeName()
        tdsStream:Disconnect()
        instance.host = host
        Helper.AddOrMergeInstance( instance )
        table.insert( instances_host, instance )
      else
        stdnse.debug3("DiscoverBySmb \n pipe: %s\n result: %s", pipeSubPath, tostring( result ) )
      end
    end
    return (instances_host ~= nil), instances_host
  end,
  Discover = function( host )
    nmap.registry.mssql = nmap.registry.mssql or {}
    nmap.registry.mssql.discovery_performed = nmap.registry.mssql.discovery_performed or {}
    nmap.registry.mssql.discovery_performed[ host.ip ] = false
    local mutex = nmap.mutex( "discovery_performed for " .. host.ip )
    mutex( "lock" )
    local sqlDefaultPort = nmap.get_port_state( host, {number = 1433, protocol = "tcp"} ) or {number = 1433, protocol = "tcp"}
    local sqlBrowserPort = nmap.get_port_state( host, {number = 1434, protocol = "udp"} ) or {number = 1434, protocol = "udp"}
    local smbPort
    local smbPortNumber = smb.get_port( host )
    if ( smbPortNumber ) then
      smbPort = nmap.get_port_state( host, {number = smbPortNumber, protocol = "tcp"} )
    end
    local targetInstancePorts = stdnse.get_script_args( "mssql.instance-port" )
    if ( sqlBrowserPort and sqlBrowserPort.state ~= "closed" ) then
      Helper.DiscoverBySsrp( host, sqlBrowserPort )
    end
    if ( sqlDefaultPort and sqlDefaultPort.state ~= "closed" ) then
      Helper.DiscoverByTcp( host, sqlDefaultPort )
    end
    if ( smbPort ) then
      Helper.DiscoverBySmb( host, smbPort )
    end
    if ( targetInstancePorts ) then
      if ( type( targetInstancePorts ) == "string" ) then
        targetInstancePorts = { targetInstancePorts }
      end
      for _, portNumber in ipairs( targetInstancePorts ) do
        portNumber = tonumber( portNumber )
        Helper.DiscoverByTcp( host, {number = portNumber, protocol = "tcp"} )
      end
    end
    nmap.registry.mssql.discovery_performed[ host.ip ] = true
    mutex( "done" )
  end,
  GetLoginCredentials_All = function( instanceInfo )
    local credentials = instanceInfo.credentials or {}
    local credsExist = false
    for _, _ in pairs( credentials ) do
      credsExist = true
      break
    end
    if ( not credsExist ) then credentials = nil end
    if ( stdnse.get_script_args( "mssql.username" ) ) then
      credentials = credentials or {}
      local usernameArg = stdnse.get_script_args( "mssql.username" )
      local passwordArg = stdnse.get_script_args( "mssql.password" ) or ""
      credentials[ usernameArg ] = passwordArg
    end
    return credentials
  end,
  GetLoginCredentials = function( instanceInfo )
    local username = stdnse.get_script_args( "mssql.username" )
    local password = stdnse.get_script_args( "mssql.password" ) or ""
    if ( not(username) and instanceInfo.credentials ) then
      if ( instanceInfo.credentials.sa ) then
        username = "sa"
        password = instanceInfo.credentials.sa
      else
        for user, pass in pairs( instanceInfo.credentials ) do
          username = user
          password = pass
          break
        end
      end
    end
    return username, password
  end,
  Disconnect = function( self )
    if ( not(self.stream) ) then
      return false, "Not connected to server"
    end
    self.stream:Disconnect()
    self.stream = nil
    return true
  end,
  Login = function( self, username, password, database, servername )
    local loginPacket = LoginPacket:new()
    local status, result, data, errorDetail, token
    local servername = servername or "DUMMY"
    local pos = 1
    local ntlmAuth = false
    if ( not self.stream ) then
      return false, "Not connected to server"
    end
    loginPacket:SetUsername(username)
    loginPacket:SetPassword(password)
    loginPacket:SetDatabase(database)
    loginPacket:SetServer(servername)
    local domain = stdnse.get_script_args("mssql.domain")
    if (domain) then
      if ( not(HAVE_SSL) ) then return false, "mssql: OpenSSL not present" end
      ntlmAuth = true
      if (domain == 1 or domain == true ) then
        domain = "."
      end
      loginPacket:SetDomain(domain)
    end
    status, result = self.stream:Send( loginPacket:ToString() )
    if ( not(status) ) then
      return false, result
    end
    status, data, errorDetail = self.stream:Receive()
    if ( not(status) ) then
      if ( errorDetail == "NT_STATUS_PIPE_DISCONNECTED" ) then
        return false, "Bad username or password", LoginErrorType.InvalidUsernameOrPassword
      end
      return false, data
    end
    if ( ntlmAuth ) then
      local pos, nonce = Token.ParseToken( data, pos )
      local authpacket = NTAuthenticationPacket:new( username, password, domain, nonce )
      status, result = self.stream:Send( authpacket:ToString() )
      status, data = self.stream:Receive()
      if ( not(status) ) then
        return false, data
      end
    end
    while( pos < data:len() ) do
      pos, token = Token.ParseToken( data, pos )
      if ( -1 == pos ) then
        return false, token
      end
      if ( token.type == TokenType.ErrorMessage ) then
        local errorMessageLookup = {
          [LoginErrorType.AccountLockedOut] = "Account is locked out",
          [LoginErrorType.NotAssociatedWithTrustedConnection] = "User is not associated with a trusted connection (instance may allow Windows authentication only)",
          [LoginErrorType.InvalidUsernameOrPassword] = "Bad username or password",
          [LoginErrorType.PasswordExpired] = "Password is expired",
          [LoginErrorType.PasswordMustChange] = "Must change password at next logon",
        }
        local errorMessage = errorMessageLookup[ token.errno ] or string.format( "Login Failed (%s)", tostring(token.errno) )
        return false, errorMessage, token.errno
      elseif ( token.type == TokenType.LoginAcknowledgement ) then
        return true, "Login Success"
      end
    end
    return false, "Failed to process login response"
  end,
  LoginEx = function( self, instanceInfo, database, servername )
    local servername = servername or instanceInfo.host.ip
    local username, password = Helper.GetLoginCredentials( instanceInfo )
    if ( not username ) then
      return false, "No login credentials"
    end
    return self:Login( username, password, database, servername )
  end,
  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
    while( pos < data:len() ) do
      local rowtag = string.unpack("B", 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
      rowtag, pos = string.unpack("B", 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
            if not ( colinfo[i].type == 106 or colinfo[i].type == 108) then
              pos, val = ColumnData.Parse[colinfo[i].type](data, pos)
            else
              pos, val = ColumnData.Parse[colinfo[i].type]( colinfo[i].precision,  colinfo[i].scale, data, pos)
            end
            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,
  GetInstanceVersion = function( instanceInfo )
    if ( not instanceInfo.host or not (instanceInfo:HasNetworkProtocols()) ) then return false, nil end
    local status, response, version
    local tdsStream = TDSStream:new()
    status, response = tdsStream:ConnectEx( instanceInfo )
    if ( not status ) then
      stdnse.debug2("%s: Connection to %s failed: %s", "MSSQL", instanceInfo:GetName(), response or "" )
      return false, "Connect failed"
    end
    local preLoginRequest = PreLoginPacket:new()
    preLoginRequest:SetInstanceName( instanceInfo.instanceName )
    tdsStream:SetTimeout( 5000 )
    tdsStream:Send( preLoginRequest:ToBytes() )
    status, response = tdsStream:Receive()
    tdsStream:Disconnect()
    if status then
      local preLoginResponse
      status, preLoginResponse = PreLoginPacket.FromBytes( response )
      if status then
        version = preLoginResponse.versionInfo
      else
        stdnse.debug2("%s: Parsing of pre-login packet from %s failed: %s",
          "MSSQL", instanceInfo:GetName(), preLoginResponse or "" )
        return false, "Parsing failed"
      end
    else
      stdnse.debug2("%s: Receive for %s failed: %s", "MSSQL", instanceInfo:GetName(), response or "" )
      return false, "Receive failed"
    end
    return status, version
  end,
  GetTargetInstances = function( host, port )
    if ( port ) then
      local status = true
      local instance = Helper.GetDiscoveredInstances( host, port )
      if ( not instance ) then
        status, instance = Helper.DiscoverByTcp( host, port )
      end
      if ( instance ) then
        return true, instance
      else
        return false, "No SQL Server instance detected on this port"
      end
    else
      local targetInstanceNames = stdnse.get_script_args( "mssql.instance-name" )
      local targetInstancePorts = stdnse.get_script_args( "mssql.instance-port" )
      local targetAllInstances = stdnse.get_script_args( "mssql.instance-all" )
      if ( targetInstanceNames and targetInstancePorts ) then
        return false, "Connections can be made either by instance name or port."
      end
      if ( targetAllInstances and ( targetInstanceNames or targetInstancePorts ) ) then
        return false, "All instances cannot be specified together with an instance name or port."
      end
      if ( not (targetInstanceNames or targetInstancePorts or targetAllInstances) ) then
        return false, "No instance(s) specified."
      end
      if ( not Helper.WasDiscoveryPerformed( host ) ) then
        stdnse.debug2("%s: Discovery has not been performed prior to GetTargetInstances() call. Performing discovery now.", "MSSQL" )
        Helper.Discover( host )
      end
      local instanceList = Helper.GetDiscoveredInstances( host )
      if ( not instanceList ) then
        return false, "No instances found on target host"
      end
      local targetInstances = {}
      if ( targetAllInstances ) then
        targetInstances = instanceList
      else
        local temp = {}
        if ( targetInstanceNames ) then
          if ( type( targetInstanceNames ) == "string" ) then
            targetInstanceNames = { targetInstanceNames }
          end
          for _, instanceName in ipairs( targetInstanceNames ) do
            temp[ string.upper( instanceName ) ] = true
          end
        end
        targetInstanceNames = temp
        temp = {}
        if ( targetInstancePorts ) then
          if ( type( targetInstancePorts ) == "string" ) then
            targetInstancePorts = { targetInstancePorts }
          end
          for _, portNumber in ipairs( targetInstancePorts ) do
            portNumber = tonumber( portNumber )
            temp[portNumber] = true
          end
        end
        targetInstancePorts = temp
        for _, instance in ipairs( instanceList ) do
          if ( instance.instanceName and targetInstanceNames[ string.upper( instance.instanceName ) ] ) then
            table.insert( targetInstances, instance )
          elseif ( instance.port and targetInstancePorts[ tonumber( instance.port.number ) ] ) then
            table.insert( targetInstances, instance )
          end
        end
      end
      if ( #targetInstances > 0 ) then
        return true, targetInstances
      else
        return false, "Specified instance(s) not found on target host"
      end
    end
  end,
  DiscoverDACPort = function(host, instanceName)
    local socket = nmap.new_socket()
    socket:set_timeout(5000)
    if ( not(socket:connect(host, 1434, "udp")) ) then
      return false, "Failed to connect to sqlbrowser service"
    end
    if ( not(socket:send(string.pack("c2z", "\x0F\x01", instanceName))) ) then
      socket:close()
      return false, "Failed to send request to sqlbrowser service"
    end
    local status, data = socket:receive_buf(match.numbytes(6), true)
    if ( not(status) ) then
      socket:close()
      return nil
    end
    socket:close()
    if ( #data < 6 ) then
      return nil
    end
    return string.unpack("<I2", data, 5)
  end,
  GetHostrule_Standard = function()
    return function( host )
      if ( stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) ~= nil ) then
        if ( Helper.WasDiscoveryPerformed( host ) ) then
          return Helper.GetDiscoveredInstances( host ) ~= nil
        else
          return true
        end
      else
        return false
      end
    end
  end,
  GetPortrule_Standard = function()
    return function( host, port )
      return ( shortport.service( "ms-sql-s" )(host, port) and
      stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) == nil)
    end
  end,
}
Auth = {
  TDS7CryptPass = function(password)
    local xormask = 0x5a5a
    return password:gsub(".", function(i)
      local c = string.byte( i ) ~ xormask
      local m1= ( c >> 4 ) & 0x0F0F
      local m2= ( c << 4 ) & 0xF0F0
      return string.pack("<I2", m1 | m2 )
    end)
  end,
  LmResponse = function( password, nonce )
    if ( not(HAVE_SSL) ) then
      stdnse.debug1("ERROR: Nmap is missing OpenSSL")
      return
    end
    password = password .. string.rep('\0', 14 - #password)
    password = password:upper()
    local str1 = string.sub(password, 1, 7)
    local str2 = string.sub(password, 8, 14)
    local key1 = openssl.DES_string_to_key(str1)
    local key2 = openssl.DES_string_to_key(str2)
    local result = openssl.encrypt("DES", key1, nil, nonce) .. openssl.encrypt("DES", key2, nil, nonce)
    result = result .. string.rep('\0', 21 - #result)
    str1 = string.sub(result, 1, 7)
    str2 = string.sub(result, 8, 14)
    local str3 = string.sub(result, 15, 21)
    key1 = openssl.DES_string_to_key(str1)
    key2 = openssl.DES_string_to_key(str2)
    local key3 = openssl.DES_string_to_key(str3)
    result = openssl.encrypt("DES", key1, nil, nonce) .. openssl.encrypt("DES", key2, nil, nonce) .. openssl.encrypt("DES", key3, nil, nonce)
    return result
  end,
  NtlmResponse = function( password, nonce )
    local lm_response, ntlm_response, mac_key = smbauth.get_password_response(nil,
      nil,
      nil,
      password,
      nil,
      "v1",
      nonce,
      false
    )
    return ntlm_response
  end,
}
Util =
{
  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
      for k, v in pairs( tbl.colinfo ) do
        table.insert( col_names, v.text)
      end
      headers = table.concat(col_names, "\t")
      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, table.concat(v, "\t") )
    end
    return new_tbl
  end,
}
return _ENV;
