"Fossies" - the Fresh Open Source Software Archive

Member "nmap-7.91/nselib/mssql.lua" (10 Oct 2020, 116286 Bytes) of package /linux/misc/nmap-7.91.tgz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Lua source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "mssql.lua": 7.90_vs_7.91.

    1 ---
    2 -- MSSQL Library supporting a very limited subset of operations.
    3 --
    4 -- The library was designed and tested against Microsoft SQL Server 2005.
    5 -- However, it should work with versions 7.0, 2000, 2005, 2008 and 2012.
    6 -- Only a minimal amount of parsers have been added for tokens, column types
    7 -- and column data in order to support the first scripts.
    8 --
    9 -- The code has been implemented based on traffic analysis and the following
   10 -- documentation:
   11 -- * SSRP Protocol Specification: http://msdn.microsoft.com/en-us/library/cc219703.aspx
   12 -- * TDS Protocol Specification: http://msdn.microsoft.com/en-us/library/dd304523.aspx
   13 -- * TDS Protocol Documentation: http://www.freetds.org/tds.html.
   14 -- * The JTDS source code: http://jtds.sourceforge.net/index.html.
   15 --
   16 -- * SSRP: Class that handles communication over the SQL Server Resolution Protocol, used for identifying instances on a host.
   17 -- * 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.
   18 -- * ColumnData: Class containing parsers for the actual column information.
   19 -- * 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.
   20 -- * QueryPacket: Class used to hold a query and convert it to a string suitable for transmission over a socket.
   21 -- * LoginPacket: Class used to hold login specific data which can easily be converted to a string suitable for transmission over a socket.
   22 -- * PreLoginPacket: Class used to (partially) implement the TDS PreLogin packet
   23 -- * 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.
   24 -- * Helper: Class which facilitates the use of the library by through action oriented functions with descriptive names.
   25 -- * Util: A "static" class containing mostly character and type conversion functions.
   26 --
   27 -- The following sample code illustrates how scripts can use the Helper class
   28 -- to interface the library:
   29 --
   30 -- <code>
   31 -- local helper = mssql.Helper:new()
   32 -- status, result = helper:Connect( host, port )
   33 -- status, result = helper:Login( username, password, "temdpb", host.ip )
   34 -- status, result = helper:Query( "SELECT name FROM master..syslogins" )
   35 -- helper:Disconnect()
   36 -- </code>
   37 --
   38 -- The following sample code illustrates how scripts can use the Helper class
   39 -- with pre-discovered instances (e.g. by <code>ms-sql-discover</code> or <code>broadcast-ms-sql-discover</code>):
   40 --
   41 -- <code>
   42 -- local instance = mssql.Helper.GetDiscoveredInstances( host, port )
   43 -- if ( instance ) then
   44 --   local helper = mssql.Helper:new()
   45 --   status, result = helper:ConnectEx( instance )
   46 --   status, result = helper:LoginEx( instance )
   47 --   status, result = helper:Query( "SELECT name FROM master..syslogins" )
   48 --   helper:Disconnect()
   49 -- end
   50 -- </code>
   51 --
   52 -- Known limitations:
   53 -- * The library does not support SSL. The foremost reason being the awkward choice of implementation where the SSL handshake is performed within the TDS data block. By default, servers support connections over non SSL connections though.
   54 -- * Version 7 and ONLY version 7 of the protocol is supported. This should cover Microsoft SQL Server 7.0 and later.
   55 -- * 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.
   56 -- * 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.
   57 -- * No functionality for languages, localization or character codepages has been considered or implemented.
   58 -- * The library does database authentication only. No OS authentication or use of the integrated security model is supported.
   59 -- * Queries using SELECT, INSERT, DELETE and EXEC of procedures have been tested while developing scripts.
   60 --
   61 -- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
   62 --
   63 -- @author Patrik Karlsson <patrik@cqure.net>
   64 -- @author Chris Woodbury
   65 --
   66 -- @args mssql.username The username to use to connect to SQL Server instances.
   67 --       This username is used by scripts taking actions that require
   68 --       authentication (e.g. <code>ms-sql-query</code>) This username (and its
   69 --       associated password) takes precedence over any credentials discovered
   70 --       by the <code>ms-sql-brute</code> and <code>ms-sql-empty-password</code>
   71 --       scripts.
   72 --
   73 -- @args mssql.password The password for <code>mssql.username</code>. If this
   74 --       argument is not given but <code>mssql.username</code>, a blank password
   75 --       is used.
   76 --
   77 -- @args mssql.instance-name The name of the instance to connect to.
   78 --
   79 -- @args mssql.instance-port The port of the instance to connect to.
   80 --
   81 -- @args mssql.instance-all Targets all SQL server instances discovered
   82 --       through the browser service.
   83 --
   84 -- @args mssql.domain The domain against which to perform integrated
   85 --       authentication. When set, the scripts assume integrated authentication
   86 --       should be performed, rather than the default sql login.
   87 --
   88 -- @args mssql.protocol The protocol to use to connect to the instance. The
   89 --       protocol may be either <code>NP</code>,<code>Named Pipes</code> or
   90 --       <code>TCP</code>.
   91 --
   92 -- @args mssql.timeout How long to wait for SQL responses. This is a number
   93 --       followed by <code>ms</code> for milliseconds, <code>s</code> for
   94 --       seconds, <code>m</code> for minutes, or <code>h</code> for hours.
   95 --       Default: <code>30s</code>.
   96 --
   97 -- @args mssql.scanned-ports-only If set, the script will only connect
   98 --       to ports that were included in the Nmap scan. This may result in
   99 --       instances not being discovered, particularly if UDP port 1434 is not
  100 --       included. Additionally, instances that are found to be running on
  101 --       ports that were not scanned (e.g. if 1434/udp is in the scan and the
  102 --       SQL Server Browser service on that port reports an instance
  103 --       listening on 43210/tcp, which was not scanned) will be reported but
  104 --       will not be stored for use by other ms-sql-* scripts.
  105 
  106 local math = require "math"
  107 local match = require "match"
  108 local nmap = require "nmap"
  109 local datetime = require "datetime"
  110 local shortport = require "shortport"
  111 local smb = require "smb"
  112 local smbauth = require "smbauth"
  113 local stdnse = require "stdnse"
  114 local strbuf = require "strbuf"
  115 local string = require "string"
  116 local table = require "table"
  117 local unicode = require "unicode"
  118 _ENV = stdnse.module("mssql", stdnse.seeall)
  119 
  120 -- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
  121 -- Revised 03/28/2010 - v0.2 - fixed incorrect token types. added 30 seconds timeout
  122 -- Revised 01/23/2011 - v0.3 - fixed parsing error in discovery code with patch
  123 --                             from Chris Woodbury
  124 -- Revised 02/01/2011 - v0.4 - numerous changes and additions to support new
  125 --                             functionality in ms-sql- scripts and to be more
  126 --                             robust in parsing and handling data. (Chris Woodbury)
  127 -- Revised 02/19/2011 - v0.5 - numerous changes in script, library behaviour
  128 --                             * huge improvements in version detection
  129 --                             * added support for named pipes
  130 --                             * added support for integrated NTLMv1 authentication
  131 --
  132 --                             (Patrik Karlsson, Chris Woodbury)
  133 -- Revised 08/19/2012 - v0.6 - added multiple data types
  134 --                             * added detection and handling of null values when processing query responses from the server
  135 --                             * added DoneProc response token support
  136 --
  137 --                             (Tom Sellers)
  138 -- Updated 10/01/2012 - v0.7 - added support for 2012 and later service packs for 2005, 2008 and 2008 R2 (Rob Nicholls)
  139 -- Updated 02/06/2015 - v0.8 - added support for 2014 and later service packs for older versions (Rob Nicholls)
  140 
  141 local HAVE_SSL, openssl = pcall(require, "openssl")
  142 
  143 do
  144   namedpipes = smb.namedpipes
  145   local arg = stdnse.get_script_args( "mssql.timeout" ) or "30s"
  146 
  147   local timeout, err = stdnse.parse_timespec(arg)
  148   if not timeout then
  149     error(err)
  150   end
  151   MSSQL_TIMEOUT = timeout
  152 
  153   SCANNED_PORTS_ONLY = false
  154   if ( stdnse.get_script_args( "mssql.scanned-ports-only" ) ) then
  155     SCANNED_PORTS_ONLY = true
  156   end
  157 end
  158 
  159 -- This constant is number of seconds from 1900-01-01 to 1970-01-01
  160 local tds_offset_seconds = -2208988800 - datetime.utc_offset()
  161 
  162 -- *************************************
  163 -- Informational Classes
  164 -- *************************************
  165 
  166 --- SqlServerInstanceInfo class
  167 SqlServerInstanceInfo =
  168 {
  169   instanceName = nil,
  170   version = nil,
  171   serverName = nil,
  172   isClustered = nil,
  173   host = nil,
  174   port = nil,
  175   pipeName = nil,
  176 
  177   new = function(self,o)
  178     o = o or {}
  179     setmetatable(o, self)
  180     self.__index = self
  181     return o
  182   end,
  183 
  184   -- Compares two SqlServerInstanceInfo objects and determines whether they
  185   -- refer to the same SQL Server instance, judging by a combination of host,
  186   -- port, named pipe information and instance name.
  187   __eq = function( self, other )
  188     local areEqual
  189     if ( not (self.host and other.host) ) then
  190       -- if they don't both have host information, we certainly can't say
  191       -- whether they're the same
  192       areEqual = false
  193     else
  194       areEqual = (self.host.ip == other.host.ip)
  195     end
  196 
  197     if (self.port and other.port) then
  198       areEqual = areEqual and ( other.port.number == self.port.number and
  199         other.port.protocol == self.port.protocol )
  200     elseif (self.pipeName and other.pipeName) then
  201       areEqual = areEqual and (self.pipeName == other.pipeName)
  202     elseif (self.instanceName and other.instanceName) then
  203       areEqual = areEqual and (self.instanceName == other.instanceName)
  204     else
  205       -- if we have neither port nor named pipe info nor instance names,
  206       -- we can't say whether they're the same
  207       areEqual = false
  208     end
  209 
  210     return areEqual
  211   end,
  212 
  213   --- Merges the data from one SqlServerInstanceInfo object into another.
  214   --
  215   -- Each field in the first object is populated with the data from that field
  216   -- in second object if the first object's field is nil OR if
  217   -- <code>overwrite</code> is set to true. A special case is made for the
  218   -- <code>version</code> field, which is only overwritten in the second object
  219   -- has more reliable version information. The second object is not modified.
  220   Merge = function( self, other, overwrite )
  221     local mergeFields = { "host", "port", "instanceName", "version", "isClustered", "pipeName" }
  222     for _, fieldname in ipairs( mergeFields ) do
  223       -- Add values from other only if self doesn't have a value, or if overwrite is true
  224       if ( other[ fieldname ] ~= nil and (overwrite or self[ fieldname ] == nil) ) then
  225         self[ fieldname ] = other[ fieldname ]
  226       end
  227     end
  228     if (self.version and self.version.source == "SSRP" and
  229         other.version and other.version.Source == "SSNetLib") then
  230       self.version = other.version
  231     end
  232   end,
  233 
  234   --- Returns a name for the instance, based on the available information.
  235   --
  236   -- This may take one of the following forms:
  237   --  * HOST\INSTANCENAME
  238   --  * PIPENAME
  239   --  * HOST:PORT
  240   GetName = function( self )
  241     if (self.instanceName) then
  242       return string.format( "%s\\%s", self.host.ip or self.serverName or "[nil]", self.instanceName or "[nil]" )
  243     elseif (self.pipeName) then
  244       return string.format( "%s", self.pipeName )
  245     else
  246       return string.format( "%s:%s",  self.host.ip or self.serverName or "[nil]", (self.port and self.port.number) or "[nil]" )
  247     end
  248   end,
  249 
  250   --- Sets whether the instance is in a cluster
  251   --
  252   -- @param self
  253   -- @param isClustered Boolean true or the string "Yes" are interpreted as true;
  254   --         all other values are interpreted as false.
  255   SetIsClustered = function( self, isClustered )
  256     self.isClustered = (isClustered == true) or (isClustered == "Yes")
  257   end,
  258 
  259   --- Indicates whether this instance has networking protocols enabled, such
  260   --  that scripts could attempt to connect to it.
  261   HasNetworkProtocols = function( self )
  262     return (self.pipeName ~= nil) or (self.port and self.port.number)
  263   end,
  264 }
  265 
  266 
  267 --- SqlServerVersionInfo class
  268 SqlServerVersionInfo =
  269 {
  270   versionNumber = "", -- The full version string (e.g. "9.00.2047.00")
  271   major = nil, -- The major version (e.g. 9)
  272   minor = nil, -- The minor version (e.g. 0)
  273   build = nil, -- The build number (e.g. 2047)
  274   subBuild = nil, -- The sub-build number (e.g. 0)
  275   productName = nil, -- The product name (e.g. "SQL Server 2005")
  276   brandedVersion = nil, -- The branded version of the product (e.g. "2005")
  277   servicePackLevel = nil, -- The service pack level (e.g. "SP1")
  278   patched = nil, -- Whether patches have been applied since SP installation (true/false/nil)
  279   source = nil, -- The source of the version info (e.g. "SSRP", "SSNetLib")
  280 
  281   new = function(self,o)
  282     o = o or {}
  283     setmetatable(o, self)
  284     self.__index = self
  285     return o
  286   end,
  287 
  288   --- Sets the version using a version number string.
  289   --
  290   -- @param versionNumber a version number string (e.g. "9.00.1399.00")
  291   -- @param source a string indicating the source of the version info (e.g. "SSRP", "SSNetLib")
  292   SetVersionNumber = function(self, versionNumber, source)
  293     local major, minor, revision, subBuild
  294     if versionNumber:match( "^%d+%.%d+%.%d+.%d+" ) then
  295       major, minor, revision, subBuild = versionNumber:match( "^(%d+)%.(%d+)%.(%d+)" )
  296     elseif versionNumber:match( "^%d+%.%d+%.%d+" ) then
  297       major, minor, revision = versionNumber:match( "^(%d+)%.(%d+)%.(%d+)" )
  298     else
  299       stdnse.debug1("%s: SetVersionNumber: versionNumber is not in correct format: %s", "MSSQL", versionNumber or "nil" )
  300     end
  301 
  302     self:SetVersion( major, minor, revision, subBuild, source )
  303   end,
  304 
  305   --- Sets the version using the individual numeric components of the version
  306   --  number.
  307   --
  308   -- @param source a string indicating the source of the version info (e.g. "SSRP", "SSNetLib")
  309   SetVersion = function(self, major, minor, build, subBuild, source)
  310     self.source = source
  311     -- make sure our version numbers all end up as valid numbers
  312     self.major, self.minor, self.build, self.subBuild =
  313       tonumber( major or 0 ), tonumber( minor or 0 ), tonumber( build or 0 ), tonumber( subBuild or 0 )
  314 
  315     self.versionNumber = string.format( "%u.%02u.%u.%02u", self.major, self.minor, self.build, self.subBuild )
  316 
  317     self:_ParseVersionInfo()
  318   end,
  319 
  320   --- Using the version number, determines the product version
  321   _InferProductVersion = function(self)
  322 
  323     local VERSION_LOOKUP_TABLE = {
  324       ["^6%.0"] = "6.0", ["^6%.5"] = "6.5", ["^7%.0"] = "7.0",
  325       ["^8%.0"] = "2000", ["^9%.0"] = "2005", ["^10%.0"] = "2008",
  326       ["^10%.50"] = "2008 R2", ["^11%.0"] = "2012", ["^12%.0"] = "2014",
  327       ["^13%.0"] = "2016", ["^14%.0"] = "2017", ["^15%.0"] = "2019"
  328     }
  329 
  330     local product = ""
  331 
  332     for m, v in pairs(VERSION_LOOKUP_TABLE) do
  333       if ( self.versionNumber:match(m) ) then
  334         product = v
  335         self.brandedVersion = product
  336         break
  337       end
  338     end
  339 
  340     self.productName = ("Microsoft SQL Server %s"):format(product)
  341 
  342   end,
  343 
  344 
  345   --- Returns a lookup table that maps revision numbers to service pack and
  346   --  cumulative update levels for the applicable SQL Server version,
  347   --  e.g., {{1913, "RC1"}, {2100, "RTM"}, {2316, "RTMCU1"}, ...,
  348   --        {3000, "SP1"}, {3321, "SP1CU1"}, ..., {3368, "SP1CU4"}, ...}
  349   _GetSpLookupTable = function(self)
  350 
  351     -- Service pack lookup tables:
  352     -- For instances where a revised service pack was released, e.g. 2000 SP3a,
  353     -- we will include the build number for the original SP and the build number
  354     -- for the revision. However, leaving it like this would make it appear that
  355     -- subsequent builds were a patched version of the revision, e.g., a patch
  356     -- applied to 2000 SP3 that increased the build number to 780 would get
  357     -- displayed as "SP3a+", when it was actually SP3+. To avoid this, we will
  358     -- include an additional fake build number that combines the two.
  359     -- Source: https://sqlserverbuilds.blogspot.com/
  360     local SP_LOOKUP_TABLE = {
  361       ["6.5"] = {
  362         {201, "RTM"},
  363         {213, "SP1"},
  364         {240, "SP2"},
  365         {258, "SP3"},
  366         {281, "SP4"},
  367         {415, "SP5"},
  368         {416, "SP5a"},
  369         {417, "SP5/SP5a"},
  370       },
  371 
  372       ["7.0"] = {
  373         {623, "RTM"},
  374         {699, "SP1"},
  375         {842, "SP2"},
  376         {961, "SP3"},
  377         {1063, "SP4"},
  378       },
  379 
  380       ["2000"] = {
  381         {194, "RTM"},
  382         {384, "SP1"},
  383         {532, "SP2"},
  384         {534, "SP2"},
  385         {760, "SP3"},
  386         {766, "SP3a"},
  387         {767, "SP3/SP3a"},
  388         {2039, "SP4"},
  389       },
  390 
  391       ["2005"] = {
  392         {1399, "RTM"},
  393         {2047, "SP1"},
  394         {3042, "SP2"},
  395         {4035, "SP3"},
  396         {5000, "SP4"},
  397       },
  398 
  399       ["2008"] = {
  400         {1600, "RTM"},
  401         {2531, "SP1"},
  402         {4000, "SP2"},
  403         {5500, "SP3"},
  404         {6000, "SP4"},
  405       },
  406 
  407       ["2008 R2"] = {
  408         {1600, "RTM"},
  409         {2500, "SP1"},
  410         {4000, "SP2"},
  411         {6000, "SP3"},
  412       },
  413 
  414       ["2012"] = {
  415         {1103, "CTP1"},
  416         {1440, "CTP3"},
  417         {1750, "RC0"},
  418         {1913, "RC1"},
  419         {2100, "RTM"},
  420         {2316, "RTMCU1"},
  421         {2325, "RTMCU2"},
  422         {2332, "RTMCU3"},
  423         {2383, "RTMCU4"},
  424         {2395, "RTMCU5"},
  425         {2401, "RTMCU6"},
  426         {2405, "RTMCU7"},
  427         {2410, "RTMCU8"},
  428         {2419, "RTMCU9"},
  429         {2420, "RTMCU10"},
  430         {2424, "RTMCU11"},
  431         {3000, "SP1"},
  432         {3321, "SP1CU1"},
  433         {3339, "SP1CU2"},
  434         {3349, "SP1CU3"},
  435         {3368, "SP1CU4"},
  436         {3373, "SP1CU5"},
  437         {3381, "SP1CU6"},
  438         {3393, "SP1CU7"},
  439         {3401, "SP1CU8"},
  440         {3412, "SP1CU9"},
  441         {3431, "SP1CU10"},
  442         {3449, "SP1CU11"},
  443         {3470, "SP1CU12"},
  444         {3482, "SP1CU13"},
  445         {3486, "SP1CU14"},
  446         {3487, "SP1CU15"},
  447         {3492, "SP1CU16"},
  448         {5058, "SP2"},
  449         {5532, "SP2CU1"},
  450         {5548, "SP2CU2"},
  451         {5556, "SP2CU3"},
  452         {5569, "SP2CU4"},
  453         {5582, "SP2CU5"},
  454         {5592, "SP2CU6"},
  455         {5623, "SP2CU7"},
  456         {5634, "SP2CU8"},
  457         {5641, "SP2CU9"},
  458         {5644, "SP2CU10"},
  459         {5646, "SP2CU11"},
  460         {5649, "SP2CU12"},
  461         {5655, "SP2CU13"},
  462         {5657, "SP2CU14"},
  463         {5676, "SP2CU15"},
  464         {5678, "SP2CU16"},
  465         {6020, "SP3"},
  466         {6518, "SP3CU1"},
  467         {6523, "SP3CU2"},
  468         {6537, "SP3CU3"},
  469         {6540, "SP3CU4"},
  470         {6544, "SP3CU5"},
  471         {6567, "SP3CU6"},
  472         {6579, "SP3CU7"},
  473         {6594, "SP3CU8"},
  474         {6598, "SP3CU9"},
  475         {6607, "SP3CU10"},
  476         {7001, "SP4"},
  477       },
  478 
  479       ["2014"] = {
  480         {1524, "CTP2"},
  481         {2000, "RTM"},
  482         {2342, "RTMCU1"},
  483         {2370, "RTMCU2"},
  484         {2402, "RTMCU3"},
  485         {2430, "RTMCU4"},
  486         {2456, "RTMCU5"},
  487         {2480, "RTMCU6"},
  488         {2495, "RTMCU7"},
  489         {2546, "RTMCU8"},
  490         {2553, "RTMCU9"},
  491         {2556, "RTMCU10"},
  492         {2560, "RTMCU11"},
  493         {2564, "RTMCU12"},
  494         {2568, "RTMCU13"},
  495         {2569, "RTMCU14"},
  496         {4100, "SP1"},
  497         {4416, "SP1CU1"},
  498         {4422, "SP1CU2"},
  499         {4427, "SP1CU3"},
  500         {4436, "SP1CU4"},
  501         {4439, "SP1CU5"},
  502         {4449, "SP1CU6"},
  503         {4459, "SP1CU7"},
  504         {4468, "SP1CU8"},
  505         {4474, "SP1CU9"},
  506         {4491, "SP1CU10"},
  507         {4502, "SP1CU11"},
  508         {4511, "SP1CU12"},
  509         {4522, "SP1CU13"},
  510         {5000, "SP2"},
  511         {5511, "SP2CU1"},
  512         {5522, "SP2CU2"},
  513         {5538, "SP2CU3"},
  514         {5540, "SP2CU4"},
  515         {5546, "SP2CU5"},
  516         {5553, "SP2CU6"},
  517         {5556, "SP2CU7"},
  518         {5557, "SP2CU8"},
  519         {5563, "SP2CU9"},
  520         {5571, "SP2CU10"},
  521         {5579, "SP2CU11"},
  522         {5589, "SP2CU12"},
  523         {5590, "SP2CU13"},
  524         {5600, "SP2CU14"},
  525         {5605, "SP2CU15"},
  526         {5626, "SP2CU16"},
  527         {5632, "SP2CU17"},
  528         {5687, "SP2CU18"},
  529         {6024, "SP3"},
  530         {6205, "SP3CU1"},
  531         {6214, "SP3CU2"},
  532         {6259, "SP3CU3"},
  533         {6329, "SP3CU4"},
  534       },
  535 
  536       ["2016"] = {
  537         { 200, "CTP2"},
  538         { 300, "CTP2.1"},
  539         { 407, "CTP2.2"},
  540         { 500, "CTP2.3"},
  541         { 600, "CTP2.4"},
  542         { 700, "CTP3.0"},
  543         { 800, "CTP3.1"},
  544         { 900, "CTP3.2"},
  545         {1000, "CTP3.3"},
  546         {1100, "RC0"},
  547         {1200, "RC1"},
  548         {1300, "RC2"},
  549         {1400, "RC3"},
  550         {1601, "RTM"},
  551         {2149, "RTMCU1"},
  552         {2164, "RTMCU2"},
  553         {2186, "RTMCU3"},
  554         {2193, "RTMCU4"},
  555         {2197, "RTMCU5"},
  556         {2204, "RTMCU6"},
  557         {2210, "RTMCU7"},
  558         {2213, "RTMCU8"},
  559         {2216, "RTMCU9"},
  560         {4001, "SP1"},
  561         {4411, "SP1CU1"},
  562         {4422, "SP1CU2"},
  563         {4435, "SP1CU3"},
  564         {4446, "SP1CU4"},
  565         {4451, "SP1CU5"},
  566         {4457, "SP1CU6"},
  567         {4466, "SP1CU7"},
  568         {4474, "SP1CU8"},
  569         {4502, "SP1CU9"},
  570         {4514, "SP1CU10"},
  571         {4528, "SP1CU11"},
  572         {4541, "SP1CU12"},
  573         {4550, "SP1CU13"},
  574         {4560, "SP1CU14"},
  575         {4574, "SP1CU15"},
  576         {5026, "SP2"},
  577         {5149, "SP2CU1"},
  578         {5153, "SP2CU2"},
  579         {5216, "SP2CU3"},
  580         {5233, "SP2CU4"},
  581         {5264, "SP2CU5"},
  582         {5292, "SP2CU6"},
  583         {5337, "SP2CU7"},
  584         {5426, "SP2CU8"},
  585         {5479, "SP2CU9"},
  586         {5492, "SP2CU10"},
  587         {5598, "SP2CU11"},
  588         {5698, "SP2CU12"},
  589         {5820, "SP2CU13"},
  590       },
  591 
  592       ["2017"] = {
  593         {   1, "CTP1"},
  594         { 100, "CTP1.1"},
  595         { 200, "CTP1.2"},
  596         { 304, "CTP1.3"},
  597         { 405, "CTP1.4"},
  598         { 500, "CTP2.0"},
  599         { 600, "CTP2.1"},
  600         { 800, "RC1"},
  601         { 900, "RC2"},
  602         {1000, "RTM"},
  603         {3006, "CU1"},
  604         {3008, "CU2"},
  605         {3015, "CU3"},
  606         {3022, "CU4"},
  607         {3023, "CU5"},
  608         {3025, "CU6"},
  609         {3026, "CU7"},
  610         {3029, "CU8"},
  611         {3030, "CU9"},
  612         {3037, "CU10"},
  613         {3038, "CU11"},
  614         {3045, "CU12"},
  615         {3048, "CU13"},
  616         {3076, "CU14"},
  617         {3162, "CU15"},
  618         {3223, "CU16"},
  619         {3238, "CU17"},
  620         {3257, "CU18"},
  621         {3281, "CU19"},
  622         {3294, "CU20"},
  623         {3335, "CU21"},
  624       },
  625 
  626       ["2019"] = {
  627         {1000, "CTP2.0"},
  628         {1100, "CTP2.1"},
  629         {1200, "CTP2.2"},
  630         {1300, "CTP2.3"},
  631         {1400, "CTP2.4"},
  632         {1500, "CTP2.5"},
  633         {1600, "CTP3.0"},
  634         {1700, "CTP3.1"},
  635         {1800, "CTP3.2"},
  636         {1900, "RC1"},
  637         {2000, "RTM"},
  638         {2070, "GDR1"},
  639         {4003, "CU1"},
  640         {4013, "CU2"},
  641         {4023, "CU3"},
  642         {4033, "CU4"},
  643         {4043, "CU5"},
  644       },
  645     }
  646 
  647 
  648     if ( not self.brandedVersion ) then
  649       self:_InferProductVersion()
  650     end
  651 
  652     local spLookupTable = SP_LOOKUP_TABLE[self.brandedVersion]
  653     stdnse.debug1("brandedVersion: %s, #lookup: %d", self.brandedVersion, spLookupTable and #spLookupTable or 0)
  654 
  655     return spLookupTable
  656 
  657   end,
  658 
  659 
  660   --- Processes version data to determine (if possible) the product version,
  661   --  service pack level and patch status.
  662   _ParseVersionInfo = function(self)
  663 
  664     local spLookupTable = self:_GetSpLookupTable()
  665 
  666     if spLookupTable then
  667 
  668       local spLookupItr = 0
  669       -- Loop through the service pack levels until we find one whose revision
  670       -- number is the same as or lower than our revision number.
  671       while spLookupItr < #spLookupTable do
  672         spLookupItr = spLookupItr + 1
  673 
  674         if (spLookupTable[ spLookupItr ][1] == self.build ) then
  675           spLookupItr = spLookupItr
  676           break
  677         elseif (spLookupTable[ spLookupItr ][1] > self.build ) then
  678           -- The target revision number is lower than the first release
  679           if spLookupItr == 1 then
  680             self.servicePackLevel = "Pre-RTM"
  681           else
  682             -- we went too far - it's the previous SP, but with patches applied
  683             spLookupItr = spLookupItr - 1
  684           end
  685           break
  686         end
  687       end
  688 
  689       -- Now that we've identified the proper service pack level:
  690       if self.servicePackLevel ~= "Pre-RTM" then
  691         self.servicePackLevel = spLookupTable[ spLookupItr ][2]
  692 
  693         if ( spLookupTable[ spLookupItr ][1] == self.build ) then
  694           self.patched = false
  695         else
  696           self.patched = true
  697         end
  698       end
  699 
  700       -- Clean up some of our inferences. If the source of our revision number
  701       -- was the SSRP (SQL Server Browser) response, we need to recognize its
  702       -- limitations:
  703       --  * Versions of SQL Server prior to 2005 are reported with the RTM build
  704       --    number, regardless of the actual version (e.g. SQL Server 2000 is
  705       --    always 8.00.194).
  706       --  * Versions of SQL Server starting with 2005 (and going through at least
  707       --    2008) do better but are still only reported with the build number as
  708       --    of the last service pack (e.g. SQL Server 2005 SP3 with patches is
  709       --    still reported as 9.00.4035.00).
  710       if ( self.source == "SSRP" ) then
  711         self.patched = nil
  712 
  713         if ( self.major <= 8 ) then
  714           self.servicePackLevel = nil
  715         end
  716       end
  717     end
  718 
  719     return true
  720   end,
  721 
  722   ---
  723   ToString = function(self)
  724     local friendlyVersion = strbuf.new()
  725     if self.productName then
  726       friendlyVersion:concatbuf( self.productName )
  727       if self.servicePackLevel then
  728         friendlyVersion:concatbuf( " " )
  729         friendlyVersion:concatbuf( self.servicePackLevel )
  730       end
  731       if self.patched then
  732         friendlyVersion:concatbuf( "+" )
  733       end
  734     end
  735 
  736     return friendlyVersion:dump()
  737   end,
  738 
  739   --- Uses the information in this SqlServerVersionInformation object to
  740   --  populate the version information in an Nmap port table for a SQL Server
  741   --  TCP listener.
  742   --
  743   --  @param self A SqlServerVersionInformation object
  744   --  @param port An Nmap port table corresponding to the instance
  745   PopulateNmapPortVersion = function(self, port)
  746 
  747     port.service = "ms-sql-s"
  748     port.version = port.version or {}
  749     port.version.name = "ms-sql-s"
  750     port.version.product = self.productName
  751 
  752     local versionString = strbuf.new()
  753     if self.source ~= "SSRP" then
  754       versionString:concatbuf( self.versionNumber )
  755       if self.servicePackLevel then
  756         versionString:concatbuf( "; " )
  757         versionString:concatbuf( self.servicePackLevel )
  758       end
  759       if self.patched then
  760         versionString:concatbuf( "+" )
  761       end
  762       port.version.version = versionString:dump()
  763     end
  764 
  765     return port
  766   end,
  767 }
  768 
  769 
  770 -- *************************************
  771 -- SSRP (SQL Server Resolution Protocol)
  772 -- *************************************
  773 SSRP =
  774 {
  775   PORT = { number = 1434, protocol = "udp" },
  776   DEBUG_ID = "MSSQL-SSRP",
  777 
  778   MESSAGE_TYPE =
  779   {
  780     ClientBroadcast = 0x02,
  781     ClientUnicast = 0x03,
  782     ClientUnicastInstance = 0x04,
  783     ClientUnicastDAC = 0x0F,
  784     ServerResponse = 0x05,
  785   },
  786 
  787   --- Parses an SSRP string and returns a table containing one or more
  788   --  SqlServerInstanceInfo objects created from the parsed string.
  789   _ParseSsrpString = function( host, ssrpString )
  790     -- It would seem easier to just capture (.-;;) repeatedly, since
  791     -- each instance ends with ";;", but ";;" can also occur within the
  792     -- data, signifying an empty field (e.g. "...bv;;@COMPNAME;;tcp;1433;;...").
  793     -- So, instead, we'll split up the string ahead of time.
  794     -- See the SSRP specification for more details.
  795 
  796     local instanceStrings = {}
  797     local firstInstanceEnd, instanceString
  798     repeat
  799       firstInstanceEnd = ssrpString:find( ";ServerName;(.-);InstanceName;(.-);IsClustered;(.-);" )
  800       if firstInstanceEnd then
  801         instanceString = ssrpString:sub( 1, firstInstanceEnd )
  802         ssrpString = ssrpString:sub( firstInstanceEnd + 1 )
  803       else
  804         instanceString = ssrpString
  805       end
  806 
  807       table.insert( instanceStrings, instanceString )
  808     until (not firstInstanceEnd)
  809     stdnse.debug2("%s: SSRP Substrings:\n  %s", SSRP.DEBUG_ID, table.concat(instanceStrings , "\n  ") )
  810 
  811     local instances = {}
  812     for _, instanceString in ipairs( instanceStrings ) do
  813       local instance = SqlServerInstanceInfo:new()
  814       local version = SqlServerVersionInfo:new()
  815       instance.version = version
  816 
  817       instance.host = host
  818       instance.serverName = instanceString:match( "ServerName;(.-);")
  819       instance.instanceName = instanceString:match( "InstanceName;(.-);")
  820       instance:SetIsClustered( instanceString:match( "IsClustered;(.-);") )
  821       version:SetVersionNumber( instanceString:match( "Version;(.-);"), "SSRP" )
  822 
  823       local tcpPort = tonumber( instanceString:match( ";tcp;(.-);") )
  824       if tcpPort then instance.port = {number = tcpPort, protocol = "tcp"} end
  825 
  826       local pipeName = instanceString:match( ";np;(.-);")
  827       local status, pipeSubPath = namedpipes.get_pipe_subpath( pipeName )
  828       if status then
  829         pipeName = namedpipes.make_pipe_name( host.ip, pipeSubPath )
  830       elseif pipeName ~= nil then
  831         stdnse.debug1("%s: Invalid pipe name:\n%s", SSRP.DEBUG_ID, pipeName )
  832       end
  833       instance.pipeName = pipeName
  834 
  835       table.insert( instances, instance )
  836     end
  837 
  838     return instances
  839   end,
  840 
  841   ---
  842   _ProcessResponse = function( host, responseData )
  843     local instances
  844 
  845     local pos, messageType, dataLength = 1, nil, nil
  846     messageType, dataLength, pos = string.unpack("<BI2", responseData, 1)
  847     -- extract the response data (i.e. everything after the 3-byte header)
  848     responseData = responseData:sub(4)
  849     stdnse.debug2("%s: SSRP Data: %s", SSRP.DEBUG_ID, responseData )
  850     if ( messageType ~= SSRP.MESSAGE_TYPE.ServerResponse or
  851         dataLength ~= responseData:len() ) then
  852 
  853       stdnse.debug2("%s: Invalid SSRP response. Type: 0x%02x, Length: %d, Actual length: %d",
  854         SSRP.DEBUG_ID, messageType, dataLength, responseData:len() )
  855     else
  856       instances = SSRP._ParseSsrpString( host, responseData )
  857     end
  858 
  859     return instances
  860   end,
  861 
  862   ---  Attempts to retrieve information about SQL Server instances by querying
  863   --  the SQL Server Browser service on a host.
  864   --
  865   --  @param host A host table for the target host
  866   --  @param port (Optional) A port table for the target SQL Server Browser service
  867   --  @return (status, result) If status is true, result is a table of
  868   --    SqlServerInstanceInfo objects. If status is false, result is an
  869   --    error message.
  870   DiscoverInstances = function( host, port )
  871     port = port or SSRP.PORT
  872 
  873     if ( SCANNED_PORTS_ONLY and nmap.get_port_state( host, port ) == nil ) then
  874       stdnse.debug2("%s: Discovery disallowed: scanned-ports-only is set and port %d was not scanned", SSRP.DEBUG_ID, port.number )
  875       return false, "Discovery disallowed: scanned-ports-only"
  876     end
  877 
  878     local socket = nmap.new_socket("udp")
  879     socket:set_timeout(5000)
  880 
  881     if ( port.number ~= SSRP.PORT.number ) then
  882       stdnse.debug1("%s: DiscoverInstances() called with non-standard port (%d)", SSRP.DEBUG_ID, port.number )
  883     end
  884 
  885     local status, err = socket:connect( host, port )
  886     if ( not(status) ) then return false, err end
  887     status, err = socket:send( string.pack( "B", SSRP.MESSAGE_TYPE.ClientUnicast ) )
  888     if ( not(status) ) then return false, err end
  889 
  890     local responseData, instances_host
  891     status, responseData = socket:receive()
  892     if ( not(status) ) then return false, responseData
  893     else
  894       instances_host = SSRP._ProcessResponse( host, responseData )
  895     end
  896     socket:close()
  897 
  898     return status, instances_host
  899   end,
  900 
  901 
  902   --- Attempts to retrieve information about SQL Server instances by querying
  903   -- the SQL Server Browser service on a broadcast domain.
  904   --
  905   -- @param host A host table for the broadcast specification
  906   -- @param port (Optional) A port table for the target SQL Server Browser service
  907   -- @return (status, result) If status is true, result is a table of
  908   --         tables containing SqlServerInstanceInfo objects. The top-level table
  909   --         is indexed by IP address. If status is false, result is an
  910   --         error message.
  911   DiscoverInstances_Broadcast = function( host, port )
  912     port = port or SSRP.PORT
  913 
  914     local socket = nmap.new_socket("udp")
  915     socket:set_timeout(5000)
  916     local instances_all = {}
  917 
  918     if ( port.number ~= SSRP.PORT.number ) then
  919       stdnse.debug1("%S: DiscoverInstances_Broadcast() called with non-standard port (%d)", SSRP.DEBUG_ID, port.number )
  920     end
  921 
  922     local status, err = socket:sendto(host, port, string.pack( "B", SSRP.MESSAGE_TYPE.ClientBroadcast ))
  923     if ( not(status) ) then return false, err end
  924 
  925     while ( status ) do
  926       local responseData
  927       status, responseData = socket:receive()
  928       if ( status ) then
  929         local remoteIp, _
  930         status, _, _, remoteIp, _ = socket:get_info()
  931         local instances_host = SSRP._ProcessResponse( {ip = remoteIp, name = ""}, responseData )
  932         instances_all[ remoteIp ] = instances_host
  933       end
  934     end
  935     socket:close()
  936 
  937     return true, instances_all
  938   end,
  939 }
  940 
  941 
  942 
  943 -- *************************
  944 -- TDS (Tabular Data Stream)
  945 -- *************************
  946 
  947 -- TDS packet types
  948 PacketType =
  949 {
  950   Query = 0x01,
  951   Response = 0x04,
  952   Login = 0x10,
  953   NTAuthentication = 0x11,
  954   PreLogin = 0x12,
  955 }
  956 
  957 -- TDS response token types
  958 TokenType =
  959 {
  960   ReturnStatus         = 0x79,
  961   TDS7Results          = 0x81,
  962   ErrorMessage         = 0xAA,
  963   InformationMessage   = 0xAB,
  964   LoginAcknowledgement = 0xAD,
  965   Row                  = 0xD1,
  966   OrderBy              = 0xA9,
  967   EnvironmentChange    = 0xE3,
  968   NTLMSSP_CHALLENGE    = 0xed,
  969   Done                 = 0xFD,
  970   DoneProc             = 0xFE,
  971   DoneInProc           = 0xFF,
  972 }
  973 
  974 -- SQL Server/Sybase data types
  975 DataTypes =
  976 {
  977   SQLTEXT       = 0x23,
  978   GUIDTYPE      = 0x24,
  979   SYBINTN       = 0x26,
  980   SYBINT2       = 0x34,
  981   SYBINT4       = 0x38,
  982   SYBDATETIME   = 0x3D,
  983   NTEXTTYPE     = 0x63,
  984   BITNTYPE      = 0x68,
  985   DECIMALNTYPE  = 0x6A,
  986   NUMERICNTYPE  = 0x6C,
  987   FLTNTYPE      = 0x6D,
  988   MONEYNTYPE    = 0x6E,
  989   SYBDATETIMN   = 0x6F,
  990   XSYBVARBINARY = 0xA5,
  991   XSYBVARCHAR   = 0xA7,
  992   BIGBINARYTYPE = 0xAD,
  993   BIGCHARTYPE   = 0xAF,
  994   XSYBNVARCHAR  = 0xE7,
  995   SQLNCHAR      = 0xEF,
  996 }
  997 
  998 -- SQL Server login error codes
  999 -- See http://msdn.microsoft.com/en-us/library/ms131024.aspx
 1000 LoginErrorType =
 1001 {
 1002   AccountLockedOut = 15113,
 1003   NotAssociatedWithTrustedConnection = 18452, -- This probably means that the server is set for Windows authentication only
 1004   InvalidUsernameOrPassword = 18456,
 1005   PasswordChangeFailed_PasswordNotAllowed = 18463,
 1006   PasswordChangeFailed_PasswordTooShort = 18464,
 1007   PasswordChangeFailed_PasswordTooLong = 18465,
 1008   PasswordChangeFailed_PasswordNotComplex = 18466,
 1009   PasswordChangeFailed_PasswordFilter = 18467,
 1010   PasswordChangeFailed_UnexpectedError = 18468,
 1011   PasswordExpired = 18487,
 1012   PasswordMustChange = 18488,
 1013 }
 1014 
 1015 LoginErrorMessage = {}
 1016 for i, v in pairs(LoginErrorType) do
 1017   LoginErrorMessage[v] = i
 1018 end
 1019 
 1020 -- "static" ColumnInfo parser class
 1021 ColumnInfo =
 1022 {
 1023 
 1024   Parse =
 1025   {
 1026 
 1027     [DataTypes.SQLTEXT] = function( data, pos )
 1028       local colinfo = {}
 1029       local tmp
 1030 
 1031       colinfo.unknown, colinfo.codepage, colinfo.flags, colinfo.charset, pos = string.unpack("<I4I2I2B", data, pos )
 1032 
 1033       colinfo.tablenamelen, pos = string.unpack("<i2", data, pos )
 1034       colinfo.tablename, pos = string.unpack("c" .. (colinfo.tablenamelen * 2), data, pos)
 1035       colinfo.msglen, pos = string.unpack("<B", data, pos )
 1036       tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos)
 1037 
 1038       colinfo.text = unicode.utf16to8(tmp)
 1039 
 1040       return pos, colinfo
 1041     end,
 1042 
 1043     [DataTypes.GUIDTYPE] = function( data, pos )
 1044       return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
 1045     end,
 1046 
 1047     [DataTypes.SYBINTN] = function( data, pos )
 1048       local colinfo = {}
 1049       local tmp
 1050 
 1051       colinfo.unknown, colinfo.msglen, pos = string.unpack("<BB", data, pos)
 1052       tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos )
 1053       colinfo.text = unicode.utf16to8(tmp)
 1054 
 1055       return pos, colinfo
 1056     end,
 1057 
 1058     [DataTypes.SYBINT2] = function( data, pos )
 1059       return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos)
 1060     end,
 1061 
 1062     [DataTypes.SYBINT4] = function( data, pos )
 1063       return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos)
 1064     end,
 1065 
 1066     [DataTypes.SYBDATETIME] = function( data, pos )
 1067       local colinfo = {}
 1068       local tmp
 1069 
 1070       colinfo.msglen, pos = string.unpack("B", data, pos)
 1071       tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos )
 1072       colinfo.text = unicode.utf16to8(tmp)
 1073 
 1074       return pos, colinfo
 1075     end,
 1076 
 1077     [DataTypes.NTEXTTYPE] = function( data, pos )
 1078       return ColumnInfo.Parse[DataTypes.SQLTEXT](data, pos)
 1079     end,
 1080 
 1081     [DataTypes.BITNTYPE] = function( data, pos )
 1082       return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
 1083     end,
 1084 
 1085     [DataTypes.DECIMALNTYPE] = function( data, pos )
 1086       local colinfo = {}
 1087       local tmp
 1088 
 1089       colinfo.unknown, colinfo.precision, colinfo.scale, pos = string.unpack("<BBB", data, pos)
 1090       colinfo.msglen, pos = string.unpack("<B",data,pos)
 1091       tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos )
 1092       colinfo.text = unicode.utf16to8(tmp)
 1093 
 1094       return pos, colinfo
 1095     end,
 1096 
 1097     [DataTypes.NUMERICNTYPE] = function( data, pos )
 1098       return ColumnInfo.Parse[DataTypes.DECIMALNTYPE](data, pos)
 1099     end,
 1100 
 1101     [DataTypes.FLTNTYPE] = function( data, pos )
 1102       return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
 1103     end,
 1104 
 1105     [DataTypes.MONEYNTYPE] = function( data, pos )
 1106       return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
 1107     end,
 1108 
 1109     [DataTypes.SYBDATETIMN] = function( data, pos )
 1110       return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
 1111     end,
 1112 
 1113     [DataTypes.XSYBVARBINARY] = function( data, pos )
 1114       local colinfo = {}
 1115       local tmp
 1116 
 1117       colinfo.lts, colinfo.msglen, pos = string.unpack("<I2B", data, pos)
 1118       tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos )
 1119       colinfo.text = unicode.utf16to8(tmp)
 1120 
 1121       return pos, colinfo
 1122     end,
 1123 
 1124     [DataTypes.XSYBVARCHAR] = function( data, pos )
 1125       return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
 1126     end,
 1127 
 1128     [DataTypes.BIGBINARYTYPE] = function( data, pos )
 1129       return ColumnInfo.Parse[DataTypes.XSYBVARBINARY](data, pos)
 1130     end,
 1131 
 1132     [DataTypes.BIGCHARTYPE] = function( data, pos )
 1133       return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
 1134     end,
 1135 
 1136     [DataTypes.XSYBNVARCHAR] = function( data, pos )
 1137       local colinfo = {}
 1138       local tmp
 1139 
 1140       colinfo.lts, colinfo.codepage, colinfo.flags, colinfo.charset,
 1141       colinfo.msglen, pos = string.unpack("<I2I2I2BB", data, pos )
 1142       tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos)
 1143       colinfo.text = unicode.utf16to8(tmp)
 1144 
 1145       return pos, colinfo
 1146     end,
 1147 
 1148     [DataTypes.SQLNCHAR] = function( data, pos )
 1149       return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
 1150     end,
 1151 
 1152   }
 1153 
 1154 }
 1155 
 1156 -- "static" ColumnData parser class
 1157 ColumnData =
 1158 {
 1159   Parse = {
 1160 
 1161     [DataTypes.SQLTEXT] = function( data, pos )
 1162       local len, coldata
 1163 
 1164       -- The first len value is the size of the meta data block
 1165       -- for non-null values this seems to be 0x10 / 16 bytes
 1166       len, pos = string.unpack( "<B", data, pos )
 1167 
 1168       if ( len == 0 ) then
 1169         return pos, 'Null'
 1170       end
 1171 
 1172       -- Skip over the text update time and date values, we don't need them
 1173       -- We may come back add parsing for this information.
 1174       pos = pos + len
 1175 
 1176       -- skip a label, should be 'dummyTS'
 1177       pos = pos + 8
 1178 
 1179       -- extract the actual data
 1180       coldata, pos = string.unpack( "<s4", data, pos )
 1181 
 1182       return pos, coldata
 1183     end,
 1184 
 1185     [DataTypes.GUIDTYPE] = function( data, pos )
 1186       local len, coldata, index, nextdata
 1187       local hex = {}
 1188       len, pos = string.unpack("B", data, pos)
 1189 
 1190       if ( len == 0 ) then
 1191         return pos, 'Null'
 1192 
 1193       elseif ( len == 16 ) then
 1194         -- Mixed-endian; first 3 parts are little-endian, next 2 are big-endian
 1195         local A, B, C, D, E, pos = string.unpack("<I4I2I2>c2c6", data, pos)
 1196         coldata = ("%08x-%04x-%04x-%s-%s"):format(A, B, C, stdnse.tohex(D), stdnse.tohex(E))
 1197       else
 1198         stdnse.debug1("Unhandled length (%d) for GUIDTYPE", len)
 1199         return pos + len, 'Unsupported Data'
 1200       end
 1201 
 1202       return pos, coldata
 1203     end,
 1204 
 1205     [DataTypes.SYBINTN] = function( data, pos )
 1206       local len, num
 1207       len, pos = string.unpack("B", data, pos)
 1208 
 1209       if ( len == 0 ) then
 1210         return pos, 'Null'
 1211       elseif ( len <= 16 ) then
 1212         local v, pos = string.unpack("<i" .. len, data, pos)
 1213         return pos, v
 1214       else
 1215         return -1, ("Unhandled length (%d) for SYBINTN"):format(len)
 1216       end
 1217 
 1218       return -1, "Error"
 1219     end,
 1220 
 1221     [DataTypes.SYBINT2] = function( data, pos )
 1222       local num
 1223       num, pos = string.unpack("<I2", data, pos)
 1224 
 1225       return pos, num
 1226     end,
 1227 
 1228     [DataTypes.SYBINT4] = function( data, pos )
 1229       local num
 1230       num, pos = string.unpack("<I4", data, pos)
 1231 
 1232       return pos, num
 1233     end,
 1234 
 1235     [DataTypes.SYBDATETIME] = function( data, pos )
 1236       local hi, lo
 1237 
 1238       hi, lo, pos = string.unpack("<i4I4", data, pos)
 1239 
 1240       local result_seconds = (hi*24*60*60) + (lo/300)
 1241 
 1242       local result = datetime.format_timestamp(tds_offset_seconds + result_seconds)
 1243       return pos, result
 1244     end,
 1245 
 1246     [DataTypes.NTEXTTYPE] = function( data, pos )
 1247       local len, coldata
 1248 
 1249       -- The first len value is the size of the meta data block
 1250       len, pos = string.unpack( "<B", data, pos )
 1251 
 1252       if ( len == 0 ) then
 1253         return pos, 'Null'
 1254       end
 1255 
 1256       -- Skip over the text update time and date values, we don't need them
 1257       -- We may come back add parsing for this information.
 1258       pos = pos + len
 1259 
 1260       -- skip a label, should be 'dummyTS'
 1261       pos = pos + 8
 1262 
 1263       -- extract the actual data
 1264       coldata, pos = string.unpack( "<s4", data, pos )
 1265 
 1266       return pos, unicode.utf16to8(coldata)
 1267     end,
 1268 
 1269     [DataTypes.BITNTYPE] = function( data, pos )
 1270       return ColumnData.Parse[DataTypes.SYBINTN](data, pos)
 1271     end,
 1272 
 1273     [DataTypes.DECIMALNTYPE] = function( precision, scale, data, pos )
 1274       local len, sign, format_string, coldata
 1275 
 1276       len, pos = string.unpack("<B", data, pos)
 1277 
 1278       if ( len == 0 ) then
 1279         return pos, 'Null'
 1280       end
 1281 
 1282       sign, pos = string.unpack("<B", data, pos)
 1283 
 1284       -- subtract 1 from data len to account for sign byte
 1285       len = len - 1
 1286 
 1287       if ( len > 0 and len <= 16 ) then
 1288         coldata, pos = string.unpack("<I" .. len, data, pos)
 1289       else
 1290         stdnse.debug1("Unhandled length (%d) for DECIMALNTYPE", len)
 1291         return pos + len, 'Unsupported Data'
 1292       end
 1293 
 1294       if ( sign == 0 ) then
 1295         coldata = coldata * -1
 1296       end
 1297 
 1298       coldata = coldata * (10^-scale)
 1299       -- format the return information to reduce truncation by lua
 1300       format_string = string.format("%%.%if", scale)
 1301       coldata = string.format(format_string,coldata)
 1302 
 1303       return pos, coldata
 1304     end,
 1305 
 1306     [DataTypes.NUMERICNTYPE] = function( precision, scale, data, pos )
 1307       return ColumnData.Parse[DataTypes.DECIMALNTYPE]( precision, scale, data, pos )
 1308     end,
 1309 
 1310     [DataTypes.BITNTYPE] = function( data, pos )
 1311       return ColumnData.Parse[DataTypes.SYBINTN](data, pos)
 1312     end,
 1313 
 1314     [DataTypes.NTEXTTYPE] = function( data, pos )
 1315       local len, coldata
 1316 
 1317       -- The first len value is the size of the meta data block
 1318       len, pos = string.unpack( "<B", data, pos )
 1319 
 1320       if ( len == 0 ) then
 1321         return pos, 'Null'
 1322       end
 1323 
 1324       -- Skip over the text update time and date values, we don't need them
 1325       -- We may come back add parsing for this information.
 1326       pos = pos + len
 1327 
 1328       -- skip a label, should be 'dummyTS'
 1329       pos = pos + 8
 1330 
 1331       -- extract the actual data
 1332       coldata, pos = string.unpack( "<s4", data, pos )
 1333 
 1334       return pos, unicode.utf16to8(coldata)
 1335     end,
 1336 
 1337     [DataTypes.FLTNTYPE] = function( data, pos )
 1338       local len, coldata
 1339       len, pos = string.unpack("<B", data, pos)
 1340 
 1341       if ( len == 0 ) then
 1342         return pos, 'Null'
 1343       elseif ( len == 4 ) then
 1344         coldata, pos = string.unpack("<f", data, pos)
 1345       elseif ( len == 8 ) then
 1346         coldata, pos = string.unpack("<d", data, pos)
 1347       end
 1348 
 1349       return pos, coldata
 1350     end,
 1351 
 1352     [DataTypes.MONEYNTYPE] = function( data, pos )
 1353       local len, value, coldata, hi, lo
 1354       len, pos = string.unpack("B", data, pos)
 1355 
 1356       if ( len == 0 ) then
 1357         return pos, 'Null'
 1358       elseif ( len == 4 ) then
 1359         --type smallmoney
 1360         value, pos = string.unpack("<i4", data, pos)
 1361       elseif ( len == 8 ) then
 1362         -- type money
 1363         hi, lo, pos = string.unpack("<I4I4", data, pos)
 1364         value = ( hi * 0x100000000 ) + lo
 1365       else
 1366         return -1, ("Unhandled length (%d) for MONEYNTYPE"):format(len)
 1367       end
 1368 
 1369       -- the datatype allows for 4 decimal places after the period to support various currency types.
 1370       -- forcing to string to avoid truncation
 1371       coldata = string.format("%.4f",value/10000)
 1372 
 1373       return pos, coldata
 1374     end,
 1375 
 1376     [DataTypes.SYBDATETIMN] = function( data, pos )
 1377       local len, coldata
 1378 
 1379       len, pos = string.unpack( "<B", data, pos )
 1380 
 1381       if ( len == 0 ) then
 1382         return pos, 'Null'
 1383       elseif ( len == 4 ) then
 1384         -- format is smalldatetime
 1385         local days, mins
 1386         days, mins, pos = string.unpack("<I2I2", data, pos)
 1387 
 1388         local result_seconds = (days*24*60*60) + (mins*60)
 1389         coldata = datetime.format_timestamp(tds_offset_seconds + result_seconds)
 1390 
 1391         return pos,coldata
 1392 
 1393       elseif ( len == 8 ) then
 1394         -- format is datetime
 1395         return ColumnData.Parse[DataTypes.SYBDATETIME](data, pos)
 1396       else
 1397         return -1, ("Unhandled length (%d) for SYBDATETIMN"):format(len)
 1398       end
 1399 
 1400     end,
 1401 
 1402     [DataTypes.XSYBVARBINARY] = function( data, pos )
 1403       local len, coldata
 1404 
 1405       len, pos = string.unpack( "<I2", data, pos )
 1406 
 1407       if ( len == 65535 ) then
 1408         return pos, 'Null'
 1409       else
 1410         coldata, pos = string.unpack( "c"..len, data, pos )
 1411         return pos, "0x" .. stdnse.tohex(coldata)
 1412       end
 1413 
 1414       return -1, "Error"
 1415     end,
 1416 
 1417     [DataTypes.XSYBVARCHAR] = function( data, pos )
 1418       local len, coldata
 1419 
 1420       len, pos = string.unpack( "<I2", data, pos )
 1421       if ( len == 65535 ) then
 1422         return pos, 'Null'
 1423       end
 1424 
 1425       coldata, pos = string.unpack( "c"..len, data, pos )
 1426 
 1427       return pos, coldata
 1428     end,
 1429 
 1430     [DataTypes.BIGBINARYTYPE] = function( data, pos )
 1431       return ColumnData.Parse[DataTypes.XSYBVARBINARY](data, pos)
 1432     end,
 1433 
 1434     [DataTypes.BIGCHARTYPE] = function( data, pos )
 1435       return ColumnData.Parse[DataTypes.XSYBVARCHAR](data, pos)
 1436     end,
 1437 
 1438     [DataTypes.XSYBNVARCHAR] = function( data, pos )
 1439       local len, coldata
 1440 
 1441       len, pos = string.unpack( "<I2", data, pos )
 1442       if ( len == 65535 ) then
 1443         return pos, 'Null'
 1444       end
 1445       coldata, pos = string.unpack( "c"..len, data, pos )
 1446 
 1447       return pos, unicode.utf16to8(coldata)
 1448     end,
 1449 
 1450     [DataTypes.SQLNCHAR] = function( data, pos )
 1451       return ColumnData.Parse[DataTypes.XSYBNVARCHAR](data, pos)
 1452     end,
 1453 
 1454   }
 1455 }
 1456 
 1457 -- "static" Token parser class
 1458 Token =
 1459 {
 1460 
 1461   Parse = {
 1462     --- Parse error message tokens
 1463     --
 1464     -- @param data string containing "raw" data
 1465     -- @param pos number containing offset into data
 1466     -- @return pos number containing new offset after parse
 1467     -- @return token table containing token specific fields
 1468     [TokenType.ErrorMessage] = function( data, pos )
 1469       local token = {}
 1470       local tmp
 1471 
 1472       token.type = TokenType.ErrorMessage
 1473       token.size, token.errno, token.state, token.severity, token.errlen, pos = string.unpack( "<I2I4BBI2", data, pos )
 1474       tmp, pos = string.unpack("c" .. (token.errlen * 2), data, pos )
 1475       token.error = unicode.utf16to8(tmp)
 1476       token.srvlen, pos = string.unpack("B", data, pos)
 1477       tmp, pos = string.unpack("c" .. (token.srvlen * 2), data, pos )
 1478       token.server = unicode.utf16to8(tmp)
 1479       token.proclen, pos = string.unpack("B", data, pos)
 1480       tmp, pos = string.unpack("c" .. (token.proclen * 2), data, pos )
 1481       token.proc = unicode.utf16to8(tmp)
 1482       token.lineno, pos = string.unpack("<I2", data, pos)
 1483 
 1484       return pos, token
 1485     end,
 1486 
 1487     --- Parse environment change tokens
 1488     -- (This function is not implemented and simply moves the pos offset)
 1489     --
 1490     -- @param data string containing "raw" data
 1491     -- @param pos number containing offset into data
 1492     -- @return pos number containing new offset after parse
 1493     -- @return token table containing token specific fields
 1494     [TokenType.EnvironmentChange] = function( data, pos )
 1495       local token = {}
 1496       local tmp
 1497 
 1498       token.type = TokenType.EnvironmentChange
 1499       token.size, pos = string.unpack("<I2", data, pos)
 1500 
 1501       return pos + token.size, token
 1502     end,
 1503 
 1504     --- Parse information message tokens
 1505     --
 1506     -- @param data string containing "raw" data
 1507     -- @param pos number containing offset into data
 1508     -- @return pos number containing new offset after parse
 1509     -- @return token table containing token specific fields
 1510     [TokenType.InformationMessage] = function( data, pos )
 1511       local pos, token = Token.Parse[TokenType.ErrorMessage]( data, pos )
 1512       token.type = TokenType.InformationMessage
 1513       return pos, token
 1514     end,
 1515 
 1516     --- Parse login acknowledgment tokens
 1517     --
 1518     -- @param data string containing "raw" data
 1519     -- @param pos number containing offset into data
 1520     -- @return pos number containing new offset after parse
 1521     -- @return token table containing token specific fields
 1522     [TokenType.LoginAcknowledgement] = function( data, pos )
 1523       local token = {}
 1524       local _
 1525 
 1526       token.type = TokenType.LoginAcknowledgement
 1527       token.size, _, _, _, _, token.textlen, pos = string.unpack( "<I2BBBI2B", data, pos )
 1528       token.text, pos = string.unpack("c" .. token.textlen * 2, data, pos)
 1529       token.version, pos = string.unpack("<I4", data, pos )
 1530 
 1531       return pos, token
 1532     end,
 1533 
 1534     --- Parse done tokens
 1535     --
 1536     -- @param data string containing "raw" data
 1537     -- @param pos number containing offset into data
 1538     -- @return pos number containing new offset after parse
 1539     -- @return token table containing token specific fields
 1540     [TokenType.Done] = function( data, pos )
 1541       local token = {}
 1542 
 1543       token.type = TokenType.Done
 1544       token.flags, token.operation, token.rowcount, pos = string.unpack( "<I2I2I4", data, pos )
 1545 
 1546       return pos, token
 1547     end,
 1548 
 1549     --- Parses a DoneProc token received after executing a SP
 1550     --
 1551     -- @param data string containing "raw" data
 1552     -- @param pos number containing offset into data
 1553     -- @return pos number containing new offset after parse
 1554     -- @return token table containing token specific fields
 1555     [TokenType.DoneProc] = function( data, pos )
 1556       local token
 1557       pos, token = Token.Parse[TokenType.Done]( data, pos )
 1558       token.type = TokenType.DoneProc
 1559 
 1560       return pos, token
 1561     end,
 1562 
 1563 
 1564     --- Parses a DoneInProc token received after executing a SP
 1565     --
 1566     -- @param data string containing "raw" data
 1567     -- @param pos number containing offset into data
 1568     -- @return pos number containing new offset after parse
 1569     -- @return token table containing token specific fields
 1570     [TokenType.DoneInProc] = function( data, pos )
 1571       local token
 1572       pos, token = Token.Parse[TokenType.Done]( data, pos )
 1573       token.type = TokenType.DoneInProc
 1574 
 1575       return pos, token
 1576     end,
 1577 
 1578     --- Parses a ReturnStatus token
 1579     --
 1580     -- @param data string containing "raw" data
 1581     -- @param pos number containing offset into data
 1582     -- @return pos number containing new offset after parse
 1583     -- @return token table containing token specific fields
 1584     [TokenType.ReturnStatus] = function( data, pos )
 1585       local token = {}
 1586 
 1587       token.value, pos = string.unpack("<i4", data, pos)
 1588       token.type = TokenType.ReturnStatus
 1589       return pos, token
 1590     end,
 1591 
 1592     --- Parses a OrderBy token
 1593     --
 1594     -- @param data string containing "raw" data
 1595     -- @param pos number containing offset into data
 1596     -- @return pos number containing new offset after parse
 1597     -- @return token table containing token specific fields
 1598     [TokenType.OrderBy] = function( data, pos )
 1599       local token = {}
 1600 
 1601       token.size, pos = string.unpack("<I2", data, pos)
 1602       token.type = TokenType.OrderBy
 1603       return pos + token.size, token
 1604     end,
 1605 
 1606 
 1607     --- Parse TDS result tokens
 1608     --
 1609     -- @param data string containing "raw" data
 1610     -- @param pos number containing offset into data
 1611     -- @return pos number containing new offset after parse
 1612     -- @return token table containing token specific fields
 1613     [TokenType.TDS7Results] = function( data, pos )
 1614       local token = {}
 1615       local _
 1616 
 1617       token.type = TokenType.TDS7Results
 1618       token.count, pos = string.unpack( "<I2", data, pos )
 1619       token.colinfo = {}
 1620 
 1621       for i=1, token.count do
 1622         local colinfo = {}
 1623         local usertype, flags, ttype
 1624 
 1625         usertype, flags, ttype, pos = string.unpack("<I2I2B", data, pos )
 1626         if ( not(ColumnInfo.Parse[ttype]) ) then
 1627           return -1, ("Unhandled data type: 0x%X"):format(ttype)
 1628         end
 1629 
 1630         pos, colinfo = ColumnInfo.Parse[ttype]( data, pos )
 1631         colinfo.usertype = usertype
 1632         colinfo.flags = flags
 1633         colinfo.type = ttype
 1634 
 1635         table.insert( token.colinfo, colinfo )
 1636       end
 1637       return pos, token
 1638     end,
 1639 
 1640 
 1641     [TokenType.NTLMSSP_CHALLENGE] = function(data, pos)
 1642       local len, ntlmssp, msgtype, pos = string.unpack("<I2c8I4", data, pos)
 1643       local NTLMSSP_CHALLENGE = 2
 1644 
 1645       if ( ntlmssp ~= "NTLMSSP\0" or msgtype ~= NTLMSSP_CHALLENGE ) then
 1646         return -1, "Failed to process NTLMSSP Challenge"
 1647       end
 1648 
 1649       local ntlm_challenge = data:sub( 28, 35 )
 1650       pos = pos + len - 13
 1651       return pos, ntlm_challenge
 1652     end,
 1653   },
 1654 
 1655   --- Parses the first token at positions pos
 1656   --
 1657   -- @param data string containing "raw" data
 1658   -- @param pos number containing offset into data
 1659   -- @return pos number containing new offset after parse or -1 on error
 1660   -- @return token table containing token specific fields or error message on error
 1661   ParseToken = function( data, pos )
 1662     local ttype
 1663     ttype, pos = string.unpack("B", data, pos)
 1664     if ( not(Token.Parse[ttype]) ) then
 1665       stdnse.debug1("%s: No parser for token type 0x%X", "MSSQL", ttype )
 1666       return -1, ("No parser for token type: 0x%X"):format( ttype )
 1667     end
 1668 
 1669     return Token.Parse[ttype](data, pos)
 1670   end,
 1671 
 1672 }
 1673 
 1674 
 1675 --- QueryPacket class
 1676 QueryPacket =
 1677 {
 1678   new = function(self,o)
 1679     o = o or {}
 1680     setmetatable(o, self)
 1681     self.__index = self
 1682     return o
 1683   end,
 1684 
 1685   SetQuery = function( self, query )
 1686     self.query = query
 1687   end,
 1688 
 1689   --- Returns the query packet as string
 1690   --
 1691   -- @return string containing the authentication packet
 1692   ToString = function( self )
 1693     return PacketType.Query, unicode.utf8to16( self.query )
 1694   end,
 1695 
 1696 }
 1697 
 1698 
 1699 --- PreLoginPacket class
 1700 PreLoginPacket =
 1701 {
 1702   -- TDS pre-login option types
 1703   OPTION_TYPE = {
 1704     Version = 0x00,
 1705     Encryption = 0x01,
 1706     InstOpt = 0x02,
 1707     ThreadId = 0x03,
 1708     MARS = 0x04,
 1709     Terminator = 0xFF,
 1710   },
 1711 
 1712 
 1713   versionInfo = nil,
 1714   _requestEncryption = 0,
 1715   _instanceName = "",
 1716   _threadId = 0, -- Dummy value; will be filled in later
 1717   _requestMars = nil,
 1718 
 1719   new = function(self,o)
 1720     o = o or {}
 1721     setmetatable(o, self)
 1722     self.__index = self
 1723     return o
 1724   end,
 1725 
 1726   --- Sets the client version (default = 9.00.1399.00)
 1727   --
 1728   -- @param versionInfo A SqlServerVersionInfo object with the client version information
 1729   SetVersion = function(self, versionInfo)
 1730     self._versionInfo = versionInfo
 1731   end,
 1732 
 1733   --- Sets whether to request encryption (default = false)
 1734   --
 1735   -- @param requestEncryption A boolean indicating whether encryption will be requested
 1736   SetRequestEncryption = function(self, requestEncryption)
 1737     if requestEncryption then
 1738       self._requestEncryption = 1
 1739     else
 1740       self._requestEncryption = 0
 1741     end
 1742   end,
 1743 
 1744   --- Sets whether to request MARS support (default = undefined)
 1745   --
 1746   -- @param requestMars A boolean indicating whether MARS support will be requested
 1747   SetRequestMars = function(self, requestMars)
 1748     if requestMars then
 1749       self._requestMars = 1
 1750     else
 1751       self._requestMars = 0
 1752     end
 1753   end,
 1754 
 1755   --- Sets the instance name of the target
 1756   --
 1757   -- @param instanceName A string containing the name of the instance
 1758   SetInstanceName = function(self, instanceName)
 1759     self._instanceName = instanceName or ""
 1760   end,
 1761 
 1762   --- Returns the pre-login packet as a byte string
 1763   --
 1764   -- @return byte string containing the pre-login packet
 1765   ToBytes = function(self)
 1766     -- Lengths for the values of TDS pre-login option fields
 1767     local OPTION_LENGTH_CLIENT = {
 1768       [PreLoginPacket.OPTION_TYPE.Version] = 6,
 1769       [PreLoginPacket.OPTION_TYPE.Encryption] = 1,
 1770       [PreLoginPacket.OPTION_TYPE.InstOpt] = -1,
 1771       [PreLoginPacket.OPTION_TYPE.ThreadId] = 4,
 1772       [PreLoginPacket.OPTION_TYPE.MARS] = 1,
 1773       [PreLoginPacket.OPTION_TYPE.Terminator] = 0,
 1774     }
 1775 
 1776     local optionLength, optionType = 0, 0
 1777     local offset = 1 -- Terminator
 1778     offset = offset + 5 -- Version
 1779     offset = offset + 5 -- Encryption
 1780     offset = offset + 5 -- InstOpt
 1781     offset = offset + 5 -- ThreadId
 1782     if self._requestMars then offset = offset + 3 end -- MARS
 1783 
 1784     if not self.versionInfo then
 1785       self.versionInfo = SqlServerVersionInfo:new()
 1786       self.versionInfo:SetVersionNumber( "9.00.1399.00" )
 1787     end
 1788 
 1789     optionType = PreLoginPacket.OPTION_TYPE.Version
 1790     optionLength = OPTION_LENGTH_CLIENT[ optionType ]
 1791     local data = { string.pack( ">BI2I2", optionType, offset, optionLength ) }
 1792     offset = offset + optionLength
 1793 
 1794     optionType = PreLoginPacket.OPTION_TYPE.Encryption
 1795     optionLength = OPTION_LENGTH_CLIENT[ optionType ]
 1796     data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength )
 1797     offset = offset + optionLength
 1798 
 1799     optionType = PreLoginPacket.OPTION_TYPE.InstOpt
 1800     optionLength = #self._instanceName + 1 --(string length + null-terminator)
 1801     data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength )
 1802     offset = offset + optionLength
 1803 
 1804     optionType = PreLoginPacket.OPTION_TYPE.ThreadId
 1805     optionLength = OPTION_LENGTH_CLIENT[ optionType ]
 1806     data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength )
 1807     offset = offset + optionLength
 1808 
 1809     if self.requestMars then
 1810       optionType = PreLoginPacket.OPTION_TYPE.MARS
 1811       optionLength = OPTION_LENGTH_CLIENT[ optionType ]
 1812       data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength )
 1813       offset = offset + optionLength
 1814     end
 1815 
 1816     data[#data+1] = string.pack( "B", PreLoginPacket.OPTION_TYPE.Terminator )
 1817 
 1818     -- Now that the pre-login headers are done, write the data
 1819     data[#data+1] = string.pack( ">BBI2I2", self.versionInfo.major, self.versionInfo.minor,
 1820     self.versionInfo.build, self.versionInfo.subBuild )
 1821     data[#data+1] = string.pack( "<BzI4", self._requestEncryption, self._instanceName, self._threadId )
 1822     if self.requestMars then
 1823       data[#data+1] = string.pack( "B", self._requestMars )
 1824     end
 1825 
 1826     return PacketType.PreLogin, table.concat(data)
 1827   end,
 1828 
 1829   --- Reads a byte-string and creates a PreLoginPacket object from it. This is
 1830   -- intended to handle the server's response to a pre-login request.
 1831   FromBytes = function( bytes )
 1832     local OPTION_LENGTH_SERVER = {
 1833       [PreLoginPacket.OPTION_TYPE.Version] = 6,
 1834       [PreLoginPacket.OPTION_TYPE.Encryption] = 1,
 1835       [PreLoginPacket.OPTION_TYPE.InstOpt] = -1,
 1836       [PreLoginPacket.OPTION_TYPE.ThreadId] = 0, -- According to the TDS spec, this value should be empty from the server
 1837       [PreLoginPacket.OPTION_TYPE.MARS] = 1,
 1838       [PreLoginPacket.OPTION_TYPE.Terminator] = 0,
 1839     }
 1840 
 1841 
 1842     local status, pos = false, 1
 1843     local preLoginPacket = PreLoginPacket:new()
 1844 
 1845     while true do
 1846 
 1847       local optionType, optionPos, optionLength, optionData, expectedOptionLength, _
 1848       if pos > #bytes then
 1849         stdnse.debug2("%s: Could not extract optionType.", "MSSQL" )
 1850         return false, "Invalid pre-login response"
 1851       end
 1852       optionType, pos = ("B"):unpack(bytes, pos)
 1853       if ( optionType == PreLoginPacket.OPTION_TYPE.Terminator ) then
 1854         status = true
 1855         break
 1856       end
 1857       expectedOptionLength = OPTION_LENGTH_SERVER[ optionType ]
 1858       if ( not expectedOptionLength ) then
 1859         stdnse.debug2("%s: Unrecognized pre-login option type: %s", "MSSQL", optionType )
 1860         expectedOptionLength = -1
 1861       end
 1862 
 1863       if pos + 4 > #bytes + 1 then
 1864         stdnse.debug2("%s: Could not unpack optionPos and optionLength.", "MSSQL" )
 1865         return false, "Invalid pre-login response"
 1866       end
 1867       optionPos, optionLength, pos = (">I2I2"):unpack(bytes, pos)
 1868 
 1869       optionPos = optionPos + 1 -- convert from 0-based index to 1-based index
 1870 
 1871       if ( optionLength ~= expectedOptionLength and expectedOptionLength ~= -1 ) then
 1872         stdnse.debug2("%s: Option data is incorrect size in pre-login response. ", "MSSQL" )
 1873         stdnse.debug2("%s:   (optionType: %s) (optionLength: %s)", "MSSQL", optionType, optionLength )
 1874         return false, "Invalid pre-login response"
 1875       end
 1876       optionData = bytes:sub( optionPos, optionPos + optionLength - 1 )
 1877       if #optionData ~= optionLength then
 1878         stdnse.debug2("%s: Could not read sufficient bytes from version data.", "MSSQL" )
 1879         return false, "Invalid pre-login response"
 1880       end
 1881 
 1882       if ( optionType == PreLoginPacket.OPTION_TYPE.Version ) then
 1883         local major, minor, build, subBuild = (">BBI2I2"):unpack(optionData)
 1884         local version = SqlServerVersionInfo:new()
 1885         version:SetVersion( major, minor, build, subBuild, "SSNetLib" )
 1886         preLoginPacket.versionInfo = version
 1887       elseif ( optionType == PreLoginPacket.OPTION_TYPE.Encryption ) then
 1888         preLoginPacket:SetRequestEncryption( ("B"):unpack(optionData) )
 1889       elseif ( optionType == PreLoginPacket.OPTION_TYPE.InstOpt ) then
 1890         preLoginPacket:SetInstanceName( ("z"):unpack(optionData) )
 1891       elseif ( optionType == PreLoginPacket.OPTION_TYPE.ThreadId ) then
 1892         -- Do nothing. According to the TDS spec, this option is empty when sent from the server
 1893       elseif ( optionType == PreLoginPacket.OPTION_TYPE.MARS ) then
 1894         preLoginPacket:SetRequestMars( ("B"):unpack(optionData) )
 1895       end
 1896     end
 1897 
 1898     return status, preLoginPacket
 1899   end,
 1900 }
 1901 
 1902 
 1903 --- LoginPacket class
 1904 LoginPacket =
 1905 {
 1906 
 1907   -- options_1 possible values
 1908   -- 0x80 enable warning messages if SET LANGUAGE issued
 1909   -- 0x40 change to initial database must succeed
 1910   -- 0x20 enable warning messages if USE <database> issued
 1911   -- 0x10 enable BCP
 1912 
 1913   -- options_2 possible values
 1914   -- 0x80 enable domain login security
 1915   -- 0x40 "USER_SERVER - reserved"
 1916   -- 0x20 user type is "DQ login"
 1917   -- 0x10 user type is "replication login"
 1918   -- 0x08 "fCacheConnect"
 1919   -- 0x04 "fTranBoundary"
 1920   -- 0x02 client is an ODBC driver
 1921   -- 0x01 change to initial language must succeed
 1922   length = 0,
 1923   version = 0x71000001, -- Version 7.1
 1924   size = 0,
 1925   cli_version = 7, -- From jTDS JDBC driver
 1926   cli_pid = 0, -- Dummy value
 1927   conn_id = 0,
 1928   options_1 = 0xa0,
 1929   options_2 = 0x03,
 1930   sqltype_flag = 0,
 1931   reserved_flag= 0,
 1932   time_zone = 0,
 1933   collation = 0,
 1934 
 1935   -- Strings
 1936   client = "Nmap",
 1937   username = nil,
 1938   password = nil,
 1939   app = "Nmap NSE",
 1940   server = nil,
 1941   library = "mssql.lua",
 1942   locale = "",
 1943   database = "master", --nil,
 1944   MAC = "\x00\x00\x00\x00\x00\x00", -- should contain client MAC, jTDS uses all zeroes
 1945 
 1946   new = function(self,o)
 1947     o = o or {}
 1948     setmetatable(o, self)
 1949     self.__index = self
 1950     return o
 1951   end,
 1952 
 1953   --- Sets the username used for authentication
 1954   --
 1955   -- @param username string containing the username to user for authentication
 1956   SetUsername = function(self, username)
 1957     self.username = username
 1958   end,
 1959 
 1960   --- Sets the password used for authentication
 1961   --
 1962   -- @param password string containing the password to user for authentication
 1963   SetPassword = function(self, password)
 1964     self.password = password
 1965   end,
 1966 
 1967   --- Sets the database used in authentication
 1968   --
 1969   -- @param database string containing the database name
 1970   SetDatabase = function(self, database)
 1971     self.database = database
 1972   end,
 1973 
 1974   --- Sets the server's name used in authentication
 1975   --
 1976   -- @param server string containing the name or ip of the server
 1977   SetServer = function(self, server)
 1978     self.server = server
 1979   end,
 1980 
 1981   SetDomain = function(self, domain)
 1982     self.domain = domain
 1983   end,
 1984 
 1985   --- Returns the authentication packet as string
 1986   --
 1987   -- @return string containing the authentication packet
 1988   ToString = function(self)
 1989     local data
 1990     local offset = 86
 1991     local ntlmAuth = not(not(self.domain))
 1992     local authLen = 0
 1993 
 1994     self.cli_pid = math.random(100000)
 1995 
 1996     self.length = offset + 2 * ( self.client:len() + self.app:len() + self.server:len() + self.library:len() + self.database:len() )
 1997 
 1998     if ( ntlmAuth ) then
 1999       authLen = 32 + #self.domain
 2000       self.length = self.length + authLen
 2001       self.options_2 = self.options_2 + 0x80
 2002     else
 2003       self.length = self.length + 2 * (self.username:len() + self.password:len())
 2004     end
 2005 
 2006     data = {
 2007       string.pack("<I4I4I4I4I4I4", self.length, self.version, self.size, self.cli_version, self.cli_pid, self.conn_id ),
 2008       string.pack("BBBB", self.options_1, self.options_2, self.sqltype_flag, self.reserved_flag ),
 2009       string.pack("<I4I4", self.time_zone, self.collation ),
 2010 
 2011       -- offsets begin
 2012       string.pack("<I2I2", offset, self.client:len() ),
 2013     }
 2014     offset = offset + self.client:len() * 2
 2015 
 2016     if ( not(ntlmAuth) ) then
 2017       data[#data+1] = string.pack("<I2I2", offset, self.username:len() )
 2018 
 2019       offset = offset + self.username:len() * 2
 2020       data[#data+1] = string.pack("<I2I2", offset, self.password:len() )
 2021       offset = offset + self.password:len() * 2
 2022     else
 2023       data[#data+1] = string.pack("<I2I2", offset, 0 )
 2024       data[#data+1] = string.pack("<I2I2", offset, 0 )
 2025     end
 2026 
 2027     data[#data+1] = string.pack("<I2I2", offset, self.app:len() )
 2028     offset = offset + self.app:len() * 2
 2029 
 2030     data[#data+1] = string.pack("<I2I2", offset, self.server:len() )
 2031     offset = offset + self.server:len() * 2
 2032 
 2033     -- Offset to unused placeholder (reserved for future use in TDS spec)
 2034     data[#data+1] = string.pack("<I2I2", 0, 0 )
 2035 
 2036     data[#data+1] = string.pack("<I2I2", offset, self.library:len() )
 2037     offset = offset + self.library:len() * 2
 2038 
 2039     data[#data+1] = string.pack("<I2I2", offset, self.locale:len() )
 2040     offset = offset + self.locale:len() * 2
 2041 
 2042     data[#data+1] = string.pack("<I2I2", offset, self.database:len() )
 2043     offset = offset + self.database:len() * 2
 2044 
 2045     -- client MAC address, hardcoded to 00:00:00:00:00:00
 2046     data[#data+1] = self.MAC
 2047 
 2048     -- offset to auth info
 2049     data[#data+1] = string.pack("<I2", offset)
 2050     -- length of nt auth (should be 0 for sql auth)
 2051     data[#data+1] = string.pack("<I2", authLen)
 2052     -- next position (same as total packet length)
 2053     data[#data+1] = string.pack("<I2", self.length)
 2054     -- zero pad
 2055     data[#data+1] = string.pack("<I2", 0)
 2056 
 2057     -- Auth info wide strings
 2058     data[#data+1] = unicode.utf8to16(self.client)
 2059     if ( not(ntlmAuth) ) then
 2060       data[#data+1] = unicode.utf8to16(self.username)
 2061       data[#data+1] = Auth.TDS7CryptPass(self.password)
 2062     end
 2063     data[#data+1] = unicode.utf8to16(self.app)
 2064     data[#data+1] = unicode.utf8to16(self.server)
 2065     data[#data+1] = unicode.utf8to16(self.library)
 2066     data[#data+1] = unicode.utf8to16(self.locale)
 2067     data[#data+1] = unicode.utf8to16(self.database)
 2068 
 2069     if ( ntlmAuth ) then
 2070       local NTLMSSP_NEGOTIATE = 1
 2071       local flags = 0x0000b201
 2072       local workstation = ""
 2073 
 2074       data[#data+1] = "NTLMSSP\0"
 2075       data[#data+1] = string.pack("<I4I4", NTLMSSP_NEGOTIATE, flags)
 2076       data[#data+1] = string.pack("<I2I2I4", #self.domain, #self.domain, 32)
 2077       data[#data+1] = string.pack("<I2I2I4", #workstation, #workstation, 32)
 2078       data[#data+1] = self.domain:upper()
 2079     end
 2080 
 2081     return PacketType.Login, table.concat(data)
 2082   end,
 2083 
 2084 }
 2085 
 2086 NTAuthenticationPacket = {
 2087 
 2088   new = function(self, username, password, domain, nonce)
 2089     local o = {}
 2090     setmetatable(o, self)
 2091     o.username = username
 2092     o.domain = domain
 2093     o.nonce = nonce
 2094     o.password = password
 2095     self.__index = self
 2096     return o
 2097   end,
 2098 
 2099   ToString = function(self)
 2100     local ntlmssp = "NTLMSSP\0"
 2101     local NTLMSSP_AUTH = 3
 2102     local domain = unicode.utf8to16(self.domain:upper())
 2103     local user = unicode.utf8to16(self.username)
 2104     local hostname, sessionkey = "", ""
 2105     local flags = 0x00008201
 2106     local ntlm_response = Auth.NtlmResponse(self.password, self.nonce)
 2107     local lm_response = Auth.LmResponse(self.password, self.nonce)
 2108 
 2109     local domain_offset = 64
 2110     local username_offset = domain_offset + #domain
 2111     local lm_response_offset = username_offset + #user
 2112     local ntlm_response_offset = lm_response_offset + #lm_response
 2113     local hostname_offset = ntlm_response_offset + #ntlm_response
 2114     local sessionkey_offset = hostname_offset + #hostname
 2115 
 2116     local data = ntlmssp .. string.pack("<I4I2I2I4", NTLMSSP_AUTH, #lm_response, #lm_response, lm_response_offset)
 2117     .. string.pack("<I2I2I4", #ntlm_response, #ntlm_response, ntlm_response_offset)
 2118     .. string.pack("<I2I2I4", #domain, #domain, domain_offset)
 2119     .. string.pack("<I2I2I4", #user, #user, username_offset)
 2120     .. string.pack("<I2I2I4", #hostname, #hostname, hostname_offset)
 2121     .. string.pack("<I2I2I4", #sessionkey, #sessionkey, sessionkey_offset)
 2122     .. string.pack("<I4", flags)
 2123     .. domain
 2124     .. user
 2125     .. lm_response .. ntlm_response
 2126 
 2127     return PacketType.NTAuthentication, data
 2128   end,
 2129 
 2130 }
 2131 
 2132 -- Handles communication with SQL Server
 2133 TDSStream = {
 2134 
 2135   -- Status flag constants
 2136   MESSAGE_STATUS_FLAGS = {
 2137     Normal = 0x0,
 2138     EndOfMessage = 0x1,
 2139     IgnoreThisEvent = 0x2,
 2140     ResetConnection = 0x4,
 2141     ResetConnectionSkipTran = 0x8,
 2142   },
 2143 
 2144   _packetId = 0,
 2145   _pipe = nil,
 2146   _socket = nil,
 2147   _name = nil,
 2148 
 2149   new = function(self,o)
 2150     o = o or {}
 2151     setmetatable(o, self)
 2152     self.__index = self
 2153     return o
 2154   end,
 2155 
 2156   --- Establishes a connection to the SQL server.
 2157   --
 2158   --  @param self A mssql.Helper object
 2159   --  @param instanceInfo  A SqlServerInstanceInfo object for the instance to
 2160   --    connect to.
 2161   --  @param connectionPreference (Optional) A list containing one or both of
 2162   --    the strings "TCP" and "Named Pipes", indicating which transport
 2163   --    methods to try and in what order.
 2164   --  @param smbOverrides (Optional) An overrides table for calls to the <code>smb</code>
 2165   --    library (for use with named pipes).
 2166   ConnectEx = function( self, instanceInfo, connectionPreference, smbOverrides )
 2167     if ( self._socket ) then return false, "Already connected via TCP" end
 2168     if ( self._pipe ) then return false, "Already connected via named pipes" end
 2169     connectionPreference = connectionPreference or stdnse.get_script_args('mssql.protocol') or { "TCP", "Named Pipes" }
 2170     if ( connectionPreference and 'string' == type(connectionPreference) ) then
 2171       connectionPreference = { connectionPreference }
 2172     end
 2173 
 2174     local status, result, connectionType, errorMessage
 2175     stdnse.debug3("%s: Connection preferences for %s: %s",
 2176     "MSSQL", instanceInfo:GetName(), table.concat(connectionPreference, ", ") )
 2177 
 2178     for _, connectionType in ipairs( connectionPreference ) do
 2179       if connectionType == "TCP" then
 2180 
 2181         if not ( instanceInfo.port ) then
 2182           stdnse.debug3("%s: Cannot connect to %s via TCP because port table is not set.",
 2183           "MSSQL", instanceInfo:GetName() )
 2184           result = "No TCP port for this instance"
 2185         else
 2186           status, result = self:Connect( instanceInfo.host, instanceInfo.port )
 2187           if status then return true end
 2188         end
 2189 
 2190       elseif connectionType == "Named Pipes" or connectionType == "NP" then
 2191 
 2192         if not ( instanceInfo.pipeName ) then
 2193           stdnse.debug3("%s: Cannot connect to %s via named pipes because pipe name is not set.",
 2194           "MSSQL", instanceInfo:GetName() )
 2195           result = "No named pipe for this instance"
 2196         else
 2197           status, result = self:ConnectToNamedPipe( instanceInfo.host, instanceInfo.pipeName, smbOverrides )
 2198           if status then return true end
 2199         end
 2200 
 2201       else
 2202         stdnse.debug1("%s: Unknown connection preference: %s", "MSSQL", connectionType )
 2203         return false, ("ERROR: Unknown connection preference: %s"):format(connectionType)
 2204       end
 2205 
 2206       -- Handle any error messages
 2207       if not status then
 2208         if errorMessage then
 2209           errorMessage = string.format( "%s, %s: %s", errorMessage, connectionType, result or "nil" )
 2210         else
 2211           errorMessage = string.format( "%s: %s", connectionType, result or "nil" )
 2212         end
 2213       end
 2214     end
 2215 
 2216     if not errorMessage then
 2217       errorMessage = string.format( "%s: None of the preferred connection types are available for %s\\%s",
 2218       "MSSQL", instanceInfo:GetName() )
 2219     end
 2220 
 2221     return false, errorMessage
 2222   end,
 2223 
 2224   --- Establishes a connection to the SQL server
 2225   --
 2226   -- @param host A host table for the target host
 2227   -- @param pipePath The path to the named pipe of the target SQL Server
 2228   --         (e.g. "\MSSQL$SQLEXPRESS\sql\query"). If nil, "\sql\query\" is used.
 2229   -- @param smbOverrides (Optional) An overrides table for calls to the <code>smb</code>
 2230   --        library (for use with named pipes).
 2231   -- @return status: true on success, false on failure
 2232   -- @return error_message: an error message, or nil
 2233   ConnectToNamedPipe = function( self, host, pipePath, overrides )
 2234     if ( self._socket ) then return false, "Already connected via TCP" end
 2235 
 2236     if ( SCANNED_PORTS_ONLY and smb.get_port( host ) == nil ) then
 2237       stdnse.debug2("%s: Connection disallowed: scanned-ports-only is set and no SMB port is available", "MSSQL" )
 2238       return false, "Connection disallowed: scanned-ports-only"
 2239     end
 2240 
 2241     pipePath = pipePath or "\\sql\\query"
 2242 
 2243     self._pipe = namedpipes.named_pipe:new()
 2244     local status, result = self._pipe:connect( host, pipePath, overrides )
 2245     if ( status ) then
 2246       self._name = self._pipe.pipe
 2247     else
 2248       self._pipe = nil
 2249     end
 2250 
 2251     return status, result
 2252   end,
 2253 
 2254   --- Establishes a connection to the SQL server
 2255   --
 2256   -- @param host table containing host information
 2257   -- @param port table containing port information
 2258   -- @return status true on success, false on failure
 2259   -- @return result containing error message on failure
 2260   Connect = function( self, host, port )
 2261     if ( self._pipe ) then return false, "Already connected via named pipes" end
 2262 
 2263     if ( SCANNED_PORTS_ONLY and nmap.get_port_state( host, port ) == nil ) then
 2264       stdnse.debug2("%s: Connection disallowed: scanned-ports-only is set and port %d was not scanned", "MSSQL", port.number )
 2265       return false, "Connection disallowed: scanned-ports-only"
 2266     end
 2267 
 2268     local status, result, lport, _
 2269 
 2270     self._socket = nmap.new_socket()
 2271 
 2272     -- Set the timeout to something realistic for connects
 2273     self._socket:set_timeout( 5000 )
 2274     status, result = self._socket:connect(host, port)
 2275 
 2276     if ( status ) then
 2277       -- Sometimes a Query can take a long time to respond, so we set
 2278       -- the timeout to 30 seconds. This shouldn't be a problem as the
 2279       -- library attempt to decode the protocol and avoid reading past
 2280       -- the end of the input buffer. So the only time the timeout is
 2281       -- triggered is when waiting for a response to a query.
 2282       self._socket:set_timeout( MSSQL_TIMEOUT * 1000 )
 2283 
 2284       status, _, lport, _, _ = self._socket:get_info()
 2285     end
 2286 
 2287     if ( not(status) ) then
 2288       self._socket = nil
 2289       stdnse.debug2("%s: Socket connection failed on %s:%s", "MSSQL", host.ip, port.number )
 2290       return false, "Socket connection failed"
 2291     end
 2292     self._name = string.format( "%s:%s", host.ip, port.number )
 2293 
 2294     return status, result
 2295   end,
 2296 
 2297   --- Disconnects from the SQL Server
 2298   --
 2299   -- @return status true on success, false on failure
 2300   -- @return result containing error message on failure
 2301   Disconnect = function( self )
 2302     if ( self._socket ) then
 2303       local status, result = self._socket:close()
 2304       self._socket = nil
 2305       return status, result
 2306     elseif ( self._pipe ) then
 2307       local status, result = self._pipe:disconnect()
 2308       self._pipe = nil
 2309       return status, result
 2310     else
 2311       return false, "Not connected"
 2312     end
 2313   end,
 2314 
 2315   --- Sets the timeout for communication over the socket
 2316   --
 2317   -- @param timeout number containing the new socket timeout in ms
 2318   SetTimeout = function( self, timeout )
 2319     if ( self._socket ) then
 2320       self._socket:set_timeout(timeout)
 2321     else
 2322       return false, "Not connected"
 2323     end
 2324   end,
 2325 
 2326   --- Gets the name of the name pipe, or nil
 2327   GetNamedPipeName = function( self )
 2328     if ( self._pipe ) then
 2329       return self._pipe.name
 2330     else
 2331       return nil
 2332     end
 2333   end,
 2334 
 2335   --- Send a TDS request to the server
 2336   --
 2337   -- @param packetType A <code>PacketType</code>, indicating the type of TDS
 2338   --                   packet being sent.
 2339   -- @param packetData A string containing the raw data to send to the server
 2340   -- @return status true on success, false on failure
 2341   -- @return result containing error message on failure
 2342   Send = function( self, packetType, packetData )
 2343     local packetLength = packetData:len() + 8 -- +8 for TDS header
 2344     local messageStatus, spid, window = 1, 0, 0
 2345 
 2346 
 2347     if ( packetType ~= PacketType.NTAuthentication ) then self._packetId = self._packetId + 1 end
 2348     local assembledPacket = string.pack(">BBI2I2BB", packetType, messageStatus, packetLength, spid, self._packetId, window) .. packetData
 2349 
 2350     if ( self._socket ) then
 2351       return self._socket:send( assembledPacket )
 2352     elseif ( self._pipe ) then
 2353       return self._pipe:send( assembledPacket )
 2354     else
 2355       return false, "Not connected"
 2356     end
 2357   end,
 2358 
 2359   --- Receives responses from SQL Server
 2360   --
 2361   -- The function continues to read and assemble a response until the server
 2362   -- responds with the last response flag set
 2363   --
 2364   -- @return status true on success, false on failure
 2365   -- @return result containing raw data contents or error message on failure
 2366   -- @return errorDetail nil, or additional information about an error. In
 2367   --         the case of named pipes, this will be an SMB error name (e.g. NT_STATUS_PIPE_DISCONNECTED)
 2368   Receive = function( self )
 2369     local status, result, errorDetail
 2370     local combinedData, readBuffer = "", "" -- the buffer is solely for the benefit of TCP connections
 2371     local tdsPacketAvailable = true
 2372 
 2373     if not ( self._socket or self._pipe ) then
 2374       return false, "Not connected"
 2375     end
 2376 
 2377     -- Large messages (e.g. result sets) can be split across multiple TDS
 2378     -- packets from the server (which could themselves each be split across
 2379     -- multiple TCP packets or SMB messages).
 2380     while ( tdsPacketAvailable ) do
 2381       local packetType, messageStatus, packetLength, spid, window
 2382       local pos = 1
 2383 
 2384       if ( self._socket ) then
 2385         -- If there is existing data in the readBuffer, see if there's
 2386         -- enough to read the TDS headers for the next packet. If not,
 2387         -- do another read so we have something to work with.
 2388         if ( readBuffer:len() < 8 ) then
 2389           status, result = self._socket:receive_bytes(8 - readBuffer:len())
 2390           readBuffer = readBuffer .. result
 2391         end
 2392       elseif ( self._pipe ) then
 2393         -- The named pipe takes care of all of its reassembly. We don't
 2394         -- have to mess with buffers and repeatedly reading until we get
 2395         -- the whole packet. We'll still write to readBuffer, though, so
 2396         -- that the common logic can be reused.
 2397         status, result, errorDetail = self._pipe:receive()
 2398         readBuffer = result
 2399       end
 2400 
 2401       if not ( status and readBuffer ) then return false, result, errorDetail end
 2402 
 2403       -- TDS packet validity check: packet at least as long as the TDS header
 2404       if ( readBuffer:len() < 8 ) then
 2405         stdnse.debug2("%s: Receiving (%s): packet is invalid length", "MSSQL", self._name )
 2406         return false, "Server returned invalid packet"
 2407       end
 2408 
 2409       -- read in the TDS headers
 2410       packetType, messageStatus, packetLength, pos = string.unpack(">BBI2", readBuffer, pos )
 2411       spid, self._packetId, window, pos = string.unpack(">I2BB", readBuffer, pos )
 2412 
 2413       -- TDS packet validity check: packet type is Response (0x4)
 2414       if ( packetType ~= PacketType.Response ) then
 2415         stdnse.debug2("%s: Receiving (%s): Expected type 0x4 (response), but received type 0x%x",
 2416           "MSSQL", self._name, packetType )
 2417         return false, "Server returned invalid packet"
 2418       end
 2419 
 2420       if ( self._socket ) then
 2421         -- If we didn't previously read in enough data to complete this
 2422         -- TDS packet, let's do so.
 2423         while ( packetLength - readBuffer:len() > 0 ) do
 2424           status, result = self._socket:receive()
 2425           if not ( status and result ) then return false, result end
 2426           readBuffer = readBuffer .. result
 2427         end
 2428       end
 2429 
 2430       -- We've read in an apparently valid TDS packet
 2431       local thisPacketData = readBuffer:sub( pos, packetLength )
 2432       -- Append its data to that of any previous TDS packets
 2433       combinedData = combinedData .. thisPacketData
 2434       if ( self._socket ) then
 2435         -- If we read in data beyond the end of this TDS packet, save it
 2436         -- so that we can use it in the next loop.
 2437         readBuffer = readBuffer:sub( packetLength + 1 )
 2438       end
 2439 
 2440       -- TDS packet validity check: packet length matches length from header
 2441       if ( packetLength ~= (thisPacketData:len() + 8) ) then
 2442         stdnse.debug2("%s: Receiving (%s): Header reports length %d, actual length is %d",
 2443           "MSSQL", self._name, packetLength, thisPacketData:len()  )
 2444         return false, "Server returned invalid packet"
 2445       end
 2446 
 2447       -- Check the status flags in the TDS packet to see if the message is
 2448       -- continued in another TDS packet.
 2449       tdsPacketAvailable = (( messageStatus & TDSStream.MESSAGE_STATUS_FLAGS.EndOfMessage) ~=
 2450         TDSStream.MESSAGE_STATUS_FLAGS.EndOfMessage)
 2451     end
 2452 
 2453     -- return only the data section ie. without the headers
 2454     return status, combinedData
 2455   end,
 2456 
 2457 }
 2458 
 2459 --- Helper class
 2460 Helper =
 2461 {
 2462   new = function(self,o)
 2463     o = o or {}
 2464     setmetatable(o, self)
 2465     self.__index = self
 2466     return o
 2467   end,
 2468 
 2469   --- Establishes a connection to the SQL server
 2470   --
 2471   -- @param host table containing host information
 2472   -- @param port table containing port information
 2473   -- @return status true on success, false on failure
 2474   -- @return result containing error message on failure
 2475   ConnectEx = function( self, instanceInfo )
 2476     local status, result
 2477     self.stream = TDSStream:new()
 2478     status, result = self.stream:ConnectEx( instanceInfo )
 2479     if ( not(status) ) then
 2480       return false, result
 2481     end
 2482 
 2483     return true
 2484   end,
 2485 
 2486   --- Establishes a connection to the SQL server
 2487   --
 2488   -- @param host table containing host information
 2489   -- @param port table containing port information
 2490   -- @return status true on success, false on failure
 2491   -- @return result containing error message on failure
 2492   Connect = function( self, host, port )
 2493     local status, result
 2494     self.stream = TDSStream:new()
 2495     status, result = self.stream:Connect(host, port)
 2496     if ( not(status) ) then
 2497       return false, result
 2498     end
 2499 
 2500     return true
 2501   end,
 2502 
 2503   --- Returns true if discovery has been performed to detect
 2504   -- SQL Server instances on the given host
 2505   WasDiscoveryPerformed = function( host )
 2506     local mutex = nmap.mutex( "discovery_performed for " .. host.ip )
 2507     mutex( "lock" )
 2508     nmap.registry.mssql = nmap.registry.mssql or {}
 2509     nmap.registry.mssql.discovery_performed = nmap.registry.mssql.discovery_performed or {}
 2510 
 2511     local wasPerformed = nmap.registry.mssql.discovery_performed[ host.ip ] or false
 2512     mutex( "done" )
 2513 
 2514     return wasPerformed
 2515   end,
 2516 
 2517   --- Adds an instance to the list of instances kept in the Nmap registry for
 2518   --  shared use by SQL Server scripts.
 2519   --
 2520   --  If the registry already contains the instance, any new information is
 2521   --  merged into the existing instance info.  This may happen, for example,
 2522   --  when an instance is discovered via named pipes, but the same instance has
 2523   --  already been discovered via SSRP; this will prevent duplicates, where
 2524   --  possible.
 2525   AddOrMergeInstance = function( newInstance )
 2526     local instanceExists
 2527 
 2528     nmap.registry.mssql = nmap.registry.mssql or {}
 2529     nmap.registry.mssql.instances = nmap.registry.mssql.instances or {}
 2530     nmap.registry.mssql.instances[ newInstance.host.ip ] = nmap.registry.mssql.instances[ newInstance.host.ip ] or {}
 2531 
 2532     for _, existingInstance in ipairs( nmap.registry.mssql.instances[ newInstance.host.ip ] ) do
 2533       if existingInstance == newInstance then
 2534         existingInstance:Merge( newInstance )
 2535         instanceExists = true
 2536         break
 2537       end
 2538     end
 2539 
 2540     if not instanceExists then
 2541       table.insert( nmap.registry.mssql.instances[ newInstance.host.ip ], newInstance )
 2542     end
 2543   end,
 2544 
 2545   --- Gets a table containing SqlServerInstanceInfo objects discovered on
 2546   --  the specified host (and port, if specified).
 2547   --
 2548   --  @param host A host table for the target host
 2549   --  @param port (Optional) If omitted, all of the instances for the host
 2550   --    will be returned.
 2551   --  @return A table containing SqlServerInstanceInfo objects, or nil
 2552   GetDiscoveredInstances = function( host, port )
 2553     nmap.registry.mssql = nmap.registry.mssql or {}
 2554     nmap.registry.mssql.instances = nmap.registry.mssql.instances or {}
 2555     nmap.registry.mssql.instances[ host.ip ] = nmap.registry.mssql.instances[ host.ip ] or {}
 2556 
 2557     if ( not port ) then
 2558       local instances = nmap.registry.mssql.instances[ host.ip ]
 2559       if ( instances and #instances == 0 ) then instances = nil end
 2560       return instances
 2561     else
 2562       for _, instance in ipairs( nmap.registry.mssql.instances[ host.ip ] ) do
 2563         if ( instance.port and instance.port.number == port.number and
 2564           instance.port.protocol == port.protocol ) then
 2565           return { instance }
 2566         end
 2567       end
 2568 
 2569       return nil
 2570     end
 2571   end,
 2572 
 2573   --- Attempts to discover SQL Server instances using SSRP to query one or
 2574   --  more (if <code>broadcast</code> is used) SQL Server Browser services.
 2575   --
 2576   --  Any discovered instances are returned, as well as being stored for use
 2577   --  by other scripts (see <code>mssql.Helper.GetDiscoveredInstances()</code>).
 2578   --
 2579   --  @param host A host table for the target.
 2580   --  @param port (Optional) A port table for the target port. If this is nil,
 2581   --    the default SSRP port (UDP 1434) is used.
 2582   --  @param broadcast If true, this will be done with an SSRP broadcast, and
 2583   --    <code>host</code> should contain the broadcast specification (e.g.
 2584   --    ip = "255.255.255.255").
 2585   --  @return (status, result) If status is true, result is a table of
 2586   --    tables containing SqlServerInstanceInfo objects. The top-level table
 2587   --    is indexed by IP address. If status is false, result is an
 2588   --    error message.
 2589   DiscoverBySsrp = function( host, port, broadcast )
 2590 
 2591     if broadcast then
 2592       local status, result = SSRP.DiscoverInstances_Broadcast( host, port )
 2593 
 2594       if not status then
 2595         return status, result
 2596       else
 2597         for ipAddress, host in pairs( result ) do
 2598           for _, instance in ipairs( host ) do
 2599             Helper.AddOrMergeInstance( instance )
 2600             -- Give some version info back to Nmap
 2601             if ( instance.port and instance.version ) then
 2602               instance.version:PopulateNmapPortVersion( instance.port )
 2603               --nmap.set_port_version( instance.host, instance.port)
 2604             end
 2605           end
 2606         end
 2607 
 2608         return true, result
 2609       end
 2610     else
 2611       local status, result = SSRP.DiscoverInstances( host, port )
 2612 
 2613       if not status then
 2614         return status, result
 2615       else
 2616         for _, instance in ipairs( result ) do
 2617           Helper.AddOrMergeInstance( instance )
 2618           -- Give some version info back to Nmap
 2619           if ( instance.port and instance.version ) then
 2620             instance.version:PopulateNmapPortVersion( instance.port )
 2621             nmap.set_port_version( host, instance.port)
 2622           end
 2623         end
 2624 
 2625         local instances_all = {}
 2626         instances_all[ host.ip ] = result
 2627         return true, instances_all
 2628       end
 2629     end
 2630   end,
 2631 
 2632   --- Attempts to discover a SQL Server instance listening on the specified
 2633   --  port.
 2634   --
 2635   --  If an instance is discovered, it is returned, as well as being stored for
 2636   --  use by other scripts (see
 2637   --  <code>mssql.Helper.GetDiscoveredInstances()</code>).
 2638   --
 2639   --  @param host A host table for the target.
 2640   --  @param port A port table for the target port.
 2641   --  @return (status, result) If status is true, result is a table of
 2642   --    SqlServerInstanceInfo objects. If status is false, result is an
 2643   --    error message or nil.
 2644   DiscoverByTcp = function( host, port )
 2645     local version, instance, status
 2646     -- Check to see if we've already discovered an instance on this port
 2647     instance = Helper.GetDiscoveredInstances( host, port )
 2648     if ( not instance ) then
 2649       instance =  SqlServerInstanceInfo:new()
 2650       instance.host = host
 2651       instance.port = port
 2652 
 2653       status, version = Helper.GetInstanceVersion( instance )
 2654       if ( status ) then
 2655         Helper.AddOrMergeInstance( instance )
 2656         -- The point of this wasn't to get the version, just to use the
 2657         -- pre-login packet to determine whether there was a SQL Server on
 2658         -- the port. However, since we have the version now, we'll store it.
 2659         instance.version = version
 2660         -- Give some version info back to Nmap
 2661         if ( instance.port and instance.version ) then
 2662           instance.version:PopulateNmapPortVersion( instance.port )
 2663           nmap.set_port_version( host, instance.port)
 2664         end
 2665       end
 2666     end
 2667 
 2668     return (instance ~= nil), { instance }
 2669   end,
 2670 
 2671   ---  Attempts to discover SQL Server instances listening on default named
 2672   --  pipes.
 2673   --
 2674   --  Any discovered instances are returned, as well as being stored for use by
 2675   --  other scripts (see <code>mssql.Helper.GetDiscoveredInstances()</code>).
 2676   --
 2677   --  @param host A host table for the target.
 2678   --  @param port A port table for the port to connect on for SMB
 2679   --  @return (status, result) If status is true, result is a table of
 2680   --    SqlServerInstanceInfo objects. If status is false, result is an
 2681   --    error message or nil.
 2682   DiscoverBySmb = function( host, port )
 2683     local defaultPipes = {
 2684       "\\sql\\query",
 2685       "\\MSSQL$SQLEXPRESS\\sql\\query",
 2686       "\\MSSQL$SQLSERVER\\sql\\query",
 2687     }
 2688     local tdsStream = TDSStream:new()
 2689     local status, result, instances_host
 2690 
 2691     for _, pipeSubPath in ipairs( defaultPipes ) do
 2692       status, result = tdsStream:ConnectToNamedPipe( host, pipeSubPath, nil )
 2693 
 2694       if status then
 2695         instances_host = {}
 2696         local instance = SqlServerInstanceInfo:new()
 2697         instance.pipeName = tdsStream:GetNamedPipeName()
 2698         tdsStream:Disconnect()
 2699         instance.host = host
 2700 
 2701         Helper.AddOrMergeInstance( instance )
 2702         table.insert( instances_host, instance )
 2703       else
 2704         stdnse.debug3("DiscoverBySmb \n pipe: %s\n result: %s", pipeSubPath, tostring( result ) )
 2705       end
 2706     end
 2707 
 2708     return (instances_host ~= nil), instances_host
 2709   end,
 2710 
 2711   --- Attempts to discover SQL Server instances by a variety of means.
 2712   --
 2713   --  This function calls the three DiscoverBy functions, which perform the
 2714   --  actual discovery. Any discovered instances can be retrieved using
 2715   --  <code>mssql.Helper.GetDiscoveredInstances()</code>.
 2716   --
 2717   --  @param host Host table as received by the script action function
 2718   Discover = function( host )
 2719     nmap.registry.mssql = nmap.registry.mssql or {}
 2720     nmap.registry.mssql.discovery_performed = nmap.registry.mssql.discovery_performed or {}
 2721     nmap.registry.mssql.discovery_performed[ host.ip ] = false
 2722 
 2723     local mutex = nmap.mutex( "discovery_performed for " .. host.ip )
 2724     mutex( "lock" )
 2725 
 2726     local sqlDefaultPort = nmap.get_port_state( host, {number = 1433, protocol = "tcp"} ) or {number = 1433, protocol = "tcp"}
 2727     local sqlBrowserPort = nmap.get_port_state( host, {number = 1434, protocol = "udp"} ) or {number = 1434, protocol = "udp"}
 2728     local smbPort
 2729     -- smb.get_port() will return nil if no SMB port was scanned OR if SMB ports were scanned but none was open
 2730     local smbPortNumber = smb.get_port( host )
 2731     if ( smbPortNumber ) then
 2732       smbPort = nmap.get_port_state( host, {number = smbPortNumber, protocol = "tcp"} )
 2733       -- There's no use in manually setting an SMB port; if no SMB port was
 2734       -- scanned and found open, the SMB library won't work
 2735     end
 2736     -- if the user has specified ports, we'll check those too
 2737     local targetInstancePorts = stdnse.get_script_args( "mssql.instance-port" )
 2738 
 2739     if ( sqlBrowserPort and sqlBrowserPort.state ~= "closed" ) then
 2740       Helper.DiscoverBySsrp( host, sqlBrowserPort )
 2741     end
 2742     if ( sqlDefaultPort and sqlDefaultPort.state ~= "closed" ) then
 2743       Helper.DiscoverByTcp( host, sqlDefaultPort )
 2744     end
 2745     if ( smbPort ) then
 2746       Helper.DiscoverBySmb( host, smbPort )
 2747     end
 2748     if ( targetInstancePorts ) then
 2749       if ( type( targetInstancePorts ) == "string" ) then
 2750         targetInstancePorts = { targetInstancePorts }
 2751       end
 2752       for _, portNumber in ipairs( targetInstancePorts ) do
 2753         portNumber = tonumber( portNumber )
 2754         Helper.DiscoverByTcp( host, {number = portNumber, protocol = "tcp"} )
 2755       end
 2756     end
 2757 
 2758     nmap.registry.mssql.discovery_performed[ host.ip ] = true
 2759     mutex( "done" )
 2760   end,
 2761 
 2762   --- Returns all of the credentials available for the target instance,
 2763   --  including any set by the <code>mssql.username</code> and <code>mssql.password</code>
 2764   --  script arguments.
 2765   --
 2766   --  @param instanceInfo A SqlServerInstanceInfo object for the target instance
 2767   --  @return A table of usernames mapped to passwords (i.e. <code>creds[ username ] = password</code>)
 2768   GetLoginCredentials_All = function( instanceInfo )
 2769     local credentials = instanceInfo.credentials or {}
 2770     local credsExist = false
 2771     for _, _ in pairs( credentials ) do
 2772       credsExist = true
 2773       break
 2774     end
 2775     if ( not credsExist ) then credentials = nil end
 2776 
 2777     if ( stdnse.get_script_args( "mssql.username" ) ) then
 2778       credentials = credentials or {}
 2779       local usernameArg = stdnse.get_script_args( "mssql.username" )
 2780       local passwordArg = stdnse.get_script_args( "mssql.password" ) or ""
 2781       credentials[ usernameArg ] = passwordArg
 2782     end
 2783 
 2784     return credentials
 2785   end,
 2786 
 2787   ---  Returns a username-password set according to the following rules of
 2788   --  precedence:
 2789   --
 2790   --  * If the <code>mssql.username</code> and <code>mssql.password</code>
 2791   --    script arguments were set, their values are used. (If the username
 2792   --    argument was specified without the password argument, a blank
 2793   --    password is used.)
 2794   --  * If the password for the "sa" account has been discovered (e.g. by the
 2795   --    <code>ms-sql-empty-password</code> or <code>ms-sql-brute</code>
 2796   --    scripts), these credentials are used.
 2797   --  * If other credentials have been discovered, the first of these in the
 2798   --    table are used.
 2799   --  * Otherwise, nil is returned.
 2800   --
 2801   --  @param instanceInfo A SqlServerInstanceInfo object for the target instance
 2802   --  @return (username, password)
 2803   GetLoginCredentials = function( instanceInfo )
 2804 
 2805     -- First preference goes to any user-specified credentials
 2806     local username = stdnse.get_script_args( "mssql.username" )
 2807     local password = stdnse.get_script_args( "mssql.password" ) or ""
 2808 
 2809     -- Otherwise, use any valid credentials that have been discovered (e.g. by ms-sql-brute)
 2810     if ( not(username) and instanceInfo.credentials ) then
 2811       -- Second preference goes to the "sa" account
 2812       if ( instanceInfo.credentials.sa ) then
 2813         username = "sa"
 2814         password = instanceInfo.credentials.sa
 2815       else
 2816         -- ok were stuck with some n00b account, just get the first one
 2817         for user, pass in pairs( instanceInfo.credentials ) do
 2818           username = user
 2819           password = pass
 2820           break
 2821         end
 2822       end
 2823     end
 2824 
 2825     return username, password
 2826   end,
 2827 
 2828   --- Disconnects from the SQL Server
 2829   --
 2830   -- @return status true on success, false on failure
 2831   -- @return result containing error message on failure
 2832   Disconnect = function( self )
 2833     if ( not(self.stream) ) then
 2834       return false, "Not connected to server"
 2835     end
 2836 
 2837     self.stream:Disconnect()
 2838     self.stream = nil
 2839 
 2840     return true
 2841   end,
 2842 
 2843   --- Authenticates to SQL Server.
 2844   --
 2845   -- If login fails, one of the following error messages will be returned:
 2846   --  * "Password is expired"
 2847   --  * "Must change password at next logon"
 2848   --  * "Account is locked out"
 2849   --  * "Login Failed"
 2850   --
 2851   -- @param username string containing the username for authentication
 2852   -- @param password string containing the password for authentication
 2853   -- @param database string containing the database to access
 2854   -- @param servername string containing the name or ip of the remote server
 2855   -- @return status true on success, false on failure
 2856   -- @return result containing error message on failure
 2857   -- @return errorDetail nil or a <code>LoginErrorType</code> value, if available
 2858   Login = function( self, username, password, database, servername )
 2859     local loginPacket = LoginPacket:new()
 2860     local status, result, data, errorDetail, token
 2861     local servername = servername or "DUMMY"
 2862     local pos = 1
 2863     local ntlmAuth = false
 2864 
 2865     if ( not self.stream ) then
 2866       return false, "Not connected to server"
 2867     end
 2868 
 2869     loginPacket:SetUsername(username)
 2870     loginPacket:SetPassword(password)
 2871     loginPacket:SetDatabase(database)
 2872     loginPacket:SetServer(servername)
 2873 
 2874     local domain = stdnse.get_script_args("mssql.domain")
 2875     if (domain) then
 2876       if ( not(HAVE_SSL) ) then return false, "mssql: OpenSSL not present" end
 2877       ntlmAuth = true
 2878       -- if the domain was specified without an argument, set a default domain of "."
 2879       if (domain == 1 or domain == true ) then
 2880         domain = "."
 2881       end
 2882       loginPacket:SetDomain(domain)
 2883     end
 2884 
 2885     status, result = self.stream:Send( loginPacket:ToString() )
 2886     if ( not(status) ) then
 2887       return false, result
 2888     end
 2889 
 2890     status, data, errorDetail = self.stream:Receive()
 2891     if ( not(status) ) then
 2892       -- When logging in via named pipes, SQL Server will sometimes
 2893       -- disconnect the pipe if the login attempt failed (this only seems
 2894       -- to happen with non-"sa") accounts. At this point, having
 2895       -- successfully connected and sent a message, we can be reasonably
 2896       -- comfortable that a disconnected pipe indicates a failed login.
 2897       if ( errorDetail == "NT_STATUS_PIPE_DISCONNECTED" ) then
 2898         return false, "Bad username or password", LoginErrorType.InvalidUsernameOrPassword
 2899       end
 2900       return false, data
 2901     end
 2902 
 2903     if ( ntlmAuth ) then
 2904       local pos, nonce = Token.ParseToken( data, pos )
 2905       local authpacket = NTAuthenticationPacket:new( username, password, domain, nonce )
 2906       status, result = self.stream:Send( authpacket:ToString() )
 2907       status, data = self.stream:Receive()
 2908       if ( not(status) ) then
 2909         return false, data
 2910       end
 2911     end
 2912 
 2913     while( pos < data:len() ) do
 2914       pos, token = Token.ParseToken( data, pos )
 2915       if ( -1 == pos ) then
 2916         return false, token
 2917       end
 2918 
 2919       if ( token.type == TokenType.ErrorMessage ) then
 2920         local errorMessageLookup = {
 2921           [LoginErrorType.AccountLockedOut] = "Account is locked out",
 2922           [LoginErrorType.NotAssociatedWithTrustedConnection] = "User is not associated with a trusted connection (instance may allow Windows authentication only)",
 2923           [LoginErrorType.InvalidUsernameOrPassword] = "Bad username or password",
 2924           [LoginErrorType.PasswordExpired] = "Password is expired",
 2925           [LoginErrorType.PasswordMustChange] = "Must change password at next logon",
 2926         }
 2927         local errorMessage = errorMessageLookup[ token.errno ] or string.format( "Login Failed (%s)", tostring(token.errno) )
 2928 
 2929         return false, errorMessage, token.errno
 2930       elseif ( token.type == TokenType.LoginAcknowledgement ) then
 2931         return true, "Login Success"
 2932       end
 2933     end
 2934 
 2935     return false, "Failed to process login response"
 2936   end,
 2937 
 2938   --- Authenticates to SQL Server, using the credentials returned by
 2939   --  Helper.GetLoginCredentials().
 2940   --
 2941   --  If the login is rejected by the server, the error code will be returned,
 2942   --  as a number in the form of a <code>mssql.LoginErrorType</code> (for which
 2943   --  error messages can be looked up in <code>mssql.LoginErrorMessage</code>).
 2944   --
 2945   -- @param instanceInfo a SqlServerInstanceInfo object for the instance to log into
 2946   -- @param database string containing the database to access
 2947   -- @param servername string containing the name or ip of the remote server
 2948   -- @return status true on success, false on failure
 2949   -- @return result containing error code or error message
 2950   LoginEx = function( self, instanceInfo, database, servername )
 2951     local servername = servername or instanceInfo.host.ip
 2952     local username, password = Helper.GetLoginCredentials( instanceInfo )
 2953     if ( not username ) then
 2954       return false, "No login credentials"
 2955     end
 2956 
 2957     return self:Login( username, password, database, servername )
 2958   end,
 2959 
 2960   --- Performs a SQL query and parses the response
 2961   --
 2962   -- @param query string containing the SQL query
 2963   -- @return status true on success, false on failure
 2964   -- @return table containing a table of columns for each row
 2965   --         or error message on failure
 2966   Query = function( self, query )
 2967 
 2968     local queryPacket = QueryPacket:new()
 2969     local status, result, data, token, colinfo, rows
 2970     local pos = 1
 2971 
 2972     if ( nil == self.stream ) then
 2973       return false, "Not connected to server"
 2974     end
 2975 
 2976     queryPacket:SetQuery( query )
 2977     status, result = self.stream:Send( queryPacket:ToString() )
 2978     if ( not(status) ) then
 2979       return false, result
 2980     end
 2981 
 2982     status, data = self.stream:Receive()
 2983     if ( not(status) ) then
 2984       return false, data
 2985     end
 2986 
 2987     -- Iterate over tokens until we get to a rowtag
 2988     while( pos < data:len() ) do
 2989       local rowtag = string.unpack("B", data, pos)
 2990 
 2991       if ( rowtag == TokenType.Row ) then
 2992         break
 2993       end
 2994 
 2995       pos, token = Token.ParseToken( data, pos )
 2996       if ( -1 == pos ) then
 2997         return false, token
 2998       end
 2999       if ( token.type == TokenType.ErrorMessage ) then
 3000         return false, token.error
 3001       elseif ( token.type == TokenType.TDS7Results ) then
 3002         colinfo = token.colinfo
 3003       end
 3004     end
 3005 
 3006 
 3007     rows = {}
 3008 
 3009     while(true) do
 3010       local rowtag
 3011       rowtag, pos = string.unpack("B", data, pos )
 3012 
 3013       if ( rowtag ~= TokenType.Row ) then
 3014         break
 3015       end
 3016 
 3017       if ( rowtag == TokenType.Row and colinfo and #colinfo > 0 ) then
 3018         local columns = {}
 3019 
 3020         for i=1, #colinfo do
 3021           local val
 3022 
 3023           if ( ColumnData.Parse[colinfo[i].type] ) then
 3024             if not ( colinfo[i].type == 106 or colinfo[i].type == 108) then
 3025               pos, val = ColumnData.Parse[colinfo[i].type](data, pos)
 3026             else
 3027               -- decimal / numeric types need precision and scale passed.
 3028               pos, val = ColumnData.Parse[colinfo[i].type]( colinfo[i].precision,  colinfo[i].scale, data, pos)
 3029             end
 3030 
 3031             if ( -1 == pos ) then
 3032               return false, val
 3033             end
 3034             table.insert(columns, val)
 3035           else
 3036             return false, ("unknown datatype=0x%X"):format(colinfo[i].type)
 3037           end
 3038         end
 3039         table.insert(rows, columns)
 3040       end
 3041     end
 3042 
 3043     result = {}
 3044     result.rows = rows
 3045     result.colinfo = colinfo
 3046 
 3047     return true, result
 3048   end,
 3049 
 3050   --- Attempts to connect to a SQL Server instance listening on a TCP port in
 3051   --  order to determine the version of the SSNetLib DLL, which is an
 3052   --  authoritative version number for the SQL Server instance itself.
 3053   --
 3054   -- @param instanceInfo An instance of SqlServerInstanceInfo
 3055   -- @return status true on success, false on failure
 3056   -- @return versionInfo an instance of mssql.SqlServerVersionInfo, or nil
 3057   GetInstanceVersion = function( instanceInfo )
 3058 
 3059     if ( not instanceInfo.host or not (instanceInfo:HasNetworkProtocols()) ) then return false, nil end
 3060 
 3061     local status, response, version
 3062     local tdsStream = TDSStream:new()
 3063 
 3064     status, response = tdsStream:ConnectEx( instanceInfo )
 3065 
 3066     if ( not status ) then
 3067       stdnse.debug2("%s: Connection to %s failed: %s", "MSSQL", instanceInfo:GetName(), response or "" )
 3068       return false, "Connect failed"
 3069     end
 3070 
 3071     local preLoginRequest = PreLoginPacket:new()
 3072     preLoginRequest:SetInstanceName( instanceInfo.instanceName )
 3073 
 3074     tdsStream:SetTimeout( 5000 )
 3075     tdsStream:Send( preLoginRequest:ToBytes() )
 3076 
 3077     -- read in any response we might get
 3078     status, response = tdsStream:Receive()
 3079     tdsStream:Disconnect()
 3080 
 3081     if status then
 3082       local preLoginResponse
 3083       status, preLoginResponse = PreLoginPacket.FromBytes( response )
 3084       if status then
 3085         version = preLoginResponse.versionInfo
 3086       else
 3087         stdnse.debug2("%s: Parsing of pre-login packet from %s failed: %s",
 3088           "MSSQL", instanceInfo:GetName(), preLoginResponse or "" )
 3089         return false, "Parsing failed"
 3090       end
 3091     else
 3092       stdnse.debug2("%s: Receive for %s failed: %s", "MSSQL", instanceInfo:GetName(), response or "" )
 3093       return false, "Receive failed"
 3094     end
 3095 
 3096     return status, version
 3097   end,
 3098 
 3099   --- Gets a table containing SqlServerInstanceInfo objects for the instances
 3100   --  that should be run against, based on the script-args (e.g. <code>mssql.instance</code>)
 3101   --
 3102   --  @param host Host table as received by the script action function
 3103   --  @param port (Optional) Port table as received by the script action function
 3104   --  @return status True on success, false on failure
 3105   --  @return instances If status is true, this will be a table with one or
 3106   --    more SqlServerInstanceInfo objects. If status is false, this will be
 3107   --    an error message.
 3108   GetTargetInstances = function( host, port )
 3109     if ( port ) then
 3110       local status = true
 3111       local instance = Helper.GetDiscoveredInstances( host, port )
 3112 
 3113       if ( not instance ) then
 3114         status, instance = Helper.DiscoverByTcp( host, port )
 3115       end
 3116       if ( instance ) then
 3117         return true, instance
 3118       else
 3119         return false, "No SQL Server instance detected on this port"
 3120       end
 3121     else
 3122       local targetInstanceNames = stdnse.get_script_args( "mssql.instance-name" )
 3123       local targetInstancePorts = stdnse.get_script_args( "mssql.instance-port" )
 3124       local targetAllInstances = stdnse.get_script_args( "mssql.instance-all" )
 3125 
 3126       if ( targetInstanceNames and targetInstancePorts ) then
 3127         return false, "Connections can be made either by instance name or port."
 3128       end
 3129 
 3130       if ( targetAllInstances and ( targetInstanceNames or targetInstancePorts ) ) then
 3131         return false, "All instances cannot be specified together with an instance name or port."
 3132       end
 3133 
 3134       if ( not (targetInstanceNames or targetInstancePorts or targetAllInstances) ) then
 3135         return false, "No instance(s) specified."
 3136       end
 3137 
 3138       if ( not Helper.WasDiscoveryPerformed( host ) ) then
 3139         stdnse.debug2("%s: Discovery has not been performed prior to GetTargetInstances() call. Performing discovery now.", "MSSQL" )
 3140         Helper.Discover( host )
 3141       end
 3142 
 3143       local instanceList = Helper.GetDiscoveredInstances( host )
 3144       if ( not instanceList ) then
 3145         return false, "No instances found on target host"
 3146       end
 3147 
 3148       local targetInstances = {}
 3149       if ( targetAllInstances ) then
 3150         targetInstances = instanceList
 3151       else
 3152         -- We want an easy way to look up whether an instance's name was
 3153         -- in our target list. So, we'll make a table of { instanceName = true, ... }
 3154         local temp = {}
 3155         if ( targetInstanceNames ) then
 3156           if ( type( targetInstanceNames ) == "string" ) then
 3157             targetInstanceNames = { targetInstanceNames }
 3158           end
 3159           for _, instanceName in ipairs( targetInstanceNames ) do
 3160             temp[ string.upper( instanceName ) ] = true
 3161           end
 3162         end
 3163         targetInstanceNames = temp
 3164 
 3165         -- Do the same for the target ports
 3166         temp = {}
 3167         if ( targetInstancePorts ) then
 3168           if ( type( targetInstancePorts ) == "string" ) then
 3169             targetInstancePorts = { targetInstancePorts }
 3170           end
 3171           for _, portNumber in ipairs( targetInstancePorts ) do
 3172             portNumber = tonumber( portNumber )
 3173             temp[portNumber] = true
 3174           end
 3175         end
 3176         targetInstancePorts = temp
 3177 
 3178         for _, instance in ipairs( instanceList ) do
 3179           if ( instance.instanceName and targetInstanceNames[ string.upper( instance.instanceName ) ] ) then
 3180             table.insert( targetInstances, instance )
 3181           elseif ( instance.port and targetInstancePorts[ tonumber( instance.port.number ) ] ) then
 3182             table.insert( targetInstances, instance )
 3183           end
 3184         end
 3185       end
 3186 
 3187       if ( #targetInstances > 0 ) then
 3188         return true, targetInstances
 3189       else
 3190         return false, "Specified instance(s) not found on target host"
 3191       end
 3192     end
 3193   end,
 3194 
 3195   --- Queries the SQL Browser service for the DAC port of the specified instance
 3196   --
 3197   --  The DAC (Dedicated Admin Connection) port allows DBA's to connect to
 3198   --  the database when normal connection attempts fail, for example, when
 3199   --  the server is hanging, out of memory or other bad states.
 3200   --
 3201   --  @param host Host table as received by the script action function
 3202   --  @param instanceName the instance name to probe for a DAC port
 3203   --  @return number containing the DAC port on success or nil on failure
 3204   DiscoverDACPort = function(host, instanceName)
 3205     local socket = nmap.new_socket()
 3206     socket:set_timeout(5000)
 3207 
 3208     if ( not(socket:connect(host, 1434, "udp")) ) then
 3209       return false, "Failed to connect to sqlbrowser service"
 3210     end
 3211 
 3212     if ( not(socket:send(string.pack("c2z", "\x0F\x01", instanceName))) ) then
 3213       socket:close()
 3214       return false, "Failed to send request to sqlbrowser service"
 3215     end
 3216 
 3217     local status, data = socket:receive_buf(match.numbytes(6), true)
 3218     if ( not(status) ) then
 3219       socket:close()
 3220       return nil
 3221     end
 3222     socket:close()
 3223 
 3224     if ( #data < 6 ) then
 3225       return nil
 3226     end
 3227     return string.unpack("<I2", data, 5)
 3228   end,
 3229 
 3230   --- Returns a hostrule for standard SQL Server scripts, which will return
 3231   --  true if one or more instances have been targeted with the <code>mssql.instance</code>
 3232   --  script argument.
 3233   --
 3234   --  However, if a previous script has failed to find any
 3235   --  SQL Server instances on the host, the hostrule function will return
 3236   --  false to keep further scripts from running unnecessarily on that host.
 3237   --
 3238   --  @return A hostrule function (use as <code>hostrule = mssql.GetHostrule_Standard()</code>)
 3239   GetHostrule_Standard = function()
 3240     return function( host )
 3241       if ( stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) ~= nil ) then
 3242         if ( Helper.WasDiscoveryPerformed( host ) ) then
 3243           return Helper.GetDiscoveredInstances( host ) ~= nil
 3244         else
 3245           return true
 3246         end
 3247       else
 3248         return false
 3249       end
 3250     end
 3251   end,
 3252 
 3253 
 3254   ---  Returns a portrule for standard SQL Server scripts
 3255   --
 3256   -- The portrule return true if BOTH of the following conditions are met:
 3257   --  * The port has been identified as "ms-sql-s"
 3258   --  * The <code>mssql.instance</code> script argument has NOT been used
 3259   --
 3260   --  @return A portrule function (use as <code>portrule = mssql.GetPortrule_Standard()</code>)
 3261   GetPortrule_Standard = function()
 3262     return function( host, port )
 3263       return ( shortport.service( "ms-sql-s" )(host, port) and
 3264       stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) == nil)
 3265     end
 3266   end,
 3267 }
 3268 
 3269 
 3270 Auth = {
 3271 
 3272   --- Encrypts a password using the TDS7 *ultra secure* XOR encryption
 3273   --
 3274   -- @param password string containing the password to encrypt
 3275   -- @return string containing the encrypted password
 3276   TDS7CryptPass = function(password)
 3277     local xormask = 0x5a5a
 3278 
 3279     return password:gsub(".", function(i)
 3280       local c = string.byte( i ) ~ xormask
 3281       local m1= ( c >> 4 ) & 0x0F0F
 3282       local m2= ( c << 4 ) & 0xF0F0
 3283       return string.pack("<I2", m1 | m2 )
 3284     end)
 3285   end,
 3286 
 3287   LmResponse = function( password, nonce )
 3288 
 3289     if ( not(HAVE_SSL) ) then
 3290       stdnse.debug1("ERROR: Nmap is missing OpenSSL")
 3291       return
 3292     end
 3293 
 3294     password = password .. string.rep('\0', 14 - #password)
 3295 
 3296     password = password:upper()
 3297 
 3298     -- Take the first and second half of the password (note that if it's longer than 14 characters, it's truncated)
 3299     local str1 = string.sub(password, 1, 7)
 3300     local str2 = string.sub(password, 8, 14)
 3301 
 3302     -- Generate the keys
 3303     local key1 = openssl.DES_string_to_key(str1)
 3304     local key2 = openssl.DES_string_to_key(str2)
 3305 
 3306     local result = openssl.encrypt("DES", key1, nil, nonce) .. openssl.encrypt("DES", key2, nil, nonce)
 3307 
 3308     result = result .. string.rep('\0', 21 - #result)
 3309 
 3310     str1 = string.sub(result, 1, 7)
 3311     str2 = string.sub(result, 8, 14)
 3312     local str3 = string.sub(result, 15, 21)
 3313 
 3314     key1 = openssl.DES_string_to_key(str1)
 3315     key2 = openssl.DES_string_to_key(str2)
 3316     local key3 = openssl.DES_string_to_key(str3)
 3317 
 3318     result = openssl.encrypt("DES", key1, nil, nonce) .. openssl.encrypt("DES", key2, nil, nonce) .. openssl.encrypt("DES", key3, nil, nonce)
 3319     return result
 3320   end,
 3321 
 3322   NtlmResponse = function( password, nonce )
 3323     local lm_response, ntlm_response, mac_key = smbauth.get_password_response(nil,
 3324       nil,
 3325       nil,
 3326       password,
 3327       nil,
 3328       "v1",
 3329       nonce,
 3330       false
 3331     )
 3332     return ntlm_response
 3333   end,
 3334 }
 3335 
 3336 --- "static" Utility class containing mostly conversion functions
 3337 Util =
 3338 {
 3339   --- Takes a table as returned by Query and does some fancy formatting
 3340   --  better suitable for <code>stdnse.output_result</code>
 3341   --
 3342   -- @param tbl as received by <code>Helper.Query</code>
 3343   -- @param with_headers boolean true if output should contain column headers
 3344   -- @return table suitable for <code>stdnse.output_result</code>
 3345   FormatOutputTable = function ( tbl, with_headers )
 3346     local new_tbl = {}
 3347     local col_names = {}
 3348 
 3349     if ( not(tbl) ) then
 3350       return
 3351     end
 3352 
 3353     if ( with_headers and tbl.rows and #tbl.rows > 0 ) then
 3354       local headers
 3355       for k, v in pairs( tbl.colinfo ) do
 3356         table.insert( col_names, v.text)
 3357       end
 3358       headers = table.concat(col_names, "\t")
 3359       table.insert( new_tbl, headers)
 3360       headers = headers:gsub("[^%s]", "=")
 3361       table.insert( new_tbl, headers )
 3362     end
 3363 
 3364     for _, v in ipairs( tbl.rows ) do
 3365       table.insert( new_tbl, table.concat(v, "\t") )
 3366     end
 3367 
 3368     return new_tbl
 3369   end,
 3370 }
 3371 
 3372 return _ENV;