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
106local math = require "math"
107local match = require "match"
108local nmap = require "nmap"
109local datetime = require "datetime"
110local shortport = require "shortport"
111local smb = require "smb"
112local smbauth = require "smbauth"
113local stdnse = require "stdnse"
114local strbuf = require "strbuf"
115local string = require "string"
116local table = require "table"
117local 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
141local HAVE_SSL, openssl = pcall(require, "openssl")
142
143do
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
157end
158
159-- This constant is number of seconds from 1900-01-01 to 1970-01-01
160local tds_offset_seconds = -2208988800 - datetime.utc_offset()
161
162-- *************************************
163-- Informational Classes
164-- *************************************
165
166--- SqlServerInstanceInfo class
167SqlServerInstanceInfo =
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
268SqlServerVersionInfo =
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-- *************************************
773SSRP =
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
948PacketType =
949{
950  Query = 0x01,
951  Response = 0x04,
952  Login = 0x10,
953  NTAuthentication = 0x11,
954  PreLogin = 0x12,
955}
956
957-- TDS response token types
958TokenType =
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
975DataTypes =
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
1000LoginErrorType =
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
1015LoginErrorMessage = {}
1016for i, v in pairs(LoginErrorType) do
1017  LoginErrorMessage[v] = i
1018end
1019
1020-- "static" ColumnInfo parser class
1021ColumnInfo =
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
1157ColumnData =
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
1458Token =
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
1676QueryPacket =
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
1700PreLoginPacket =
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
1904LoginPacket =
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
2086NTAuthenticationPacket = {
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
2133TDSStream = {
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
2460Helper =
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
3270Auth = {
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
3337Util =
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
3372return _ENV;
3373