description = [[
Queries Microsoft SQL Server (ms-sql) for a list of databases, linked
servers, and configuration settings.
]]

author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}

require 'shortport'
require 'stdnse'
require 'mssql'

dependencies = {"ms-sql-brute", "ms-sql-empty-password"}

---
-- @args mssql.username specifies the username to use to connect to
--       the server. This option overrides any accounts found by
--       the mssql-brute and mssql-empty-password scripts.
--
-- @args mssql.password specifies the password to use to connect to
--       the server. This option overrides any accounts found by
--       the mssql-brute and mssql-empty-password scripts.
--
-- @args mssql-config.showall if set shows all configuration options.
--
-- @output
-- PORT     STATE SERVICE
-- 1433/tcp open  ms-sql-s
-- | ms-sql-config:
-- |   Databases
-- |     name      db_size owner
-- |     ====      ======= =====
-- |     nmap            2.74 MB   MAC-MINI\david
-- |   Configuration
-- |     name      value   inuse   description
-- |     ====      =====   =====   ===========
-- |     SQL Mail XPs      0       0       Enable or disable SQL Mail XPs
-- |     Database Mail XPs 0       0       Enable or disable Database Mail XPs
-- |     SMO and DMO XPs   1       1       Enable or disable SMO and DMO XPs
-- |     Ole Automation Procedures 0       0       Enable or disable Ole Automation Procedures
-- |     xp_cmdshell       0       0       Enable or disable command shell
-- |     Ad Hoc Distributed Queries        0       0       Enable or disable Ad Hoc Distributed Queries
-- |     Replication XPs   0       0       Enable or disable Replication XPs
-- |   Linked Servers
-- |     srvname   srvproduct      providername
-- |     =======   ==========      ============
-- |_    MAC-MINI  SQL Server      SQLOLEDB

-- Version 0.1
-- Created 04/02/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>

portrule = shortport.port_or_service(1433, "ms-sql-s")

action = function( host, port )

	local status, helper, response	
	local username = nmap.registry.args['mssql.username']
	local password = nmap.registry.args['mssql.password'] or ""
	local result, result_part = {}, {}
	local conf_filter = ( nmap.registry.args['mssql-config.showall'] ) and "" or " WHERE configuration_id > 16384"
	local db_filter = ( nmap.registry.args['mssql-config.showall'] ) and "" or " WHERE name NOT IN ('master','model','tempdb','msdb')"
	
	local queries = { 
		[2]={ ["Configuration"] = [[ SELECT name, 
								cast(value as varchar) value, 
								cast(value_in_use as varchar) inuse, 
								description 
								FROM sys.configurations ]] .. conf_filter }, 
		[3]={ ["Linked Servers"] = [[ SELECT srvname, srvproduct, providername 
									FROM master..sysservers 
									WHERE srvid > 0 ]] },
		[1]={ ["Databases"] = [[ CREATE TABLE #nmap_dbs(name varchar(255), db_size varchar(255), owner varchar(255), 
									dbid int, created datetime, status varchar(512), compatibility_level int )
								INSERT INTO #nmap_dbs EXEC sp_helpdb
								SELECT name, db_size, owner 
									FROM #nmap_dbs ]] .. db_filter .. [[
								DROP DATABASE #nmap_dbs ]] }
	}
	
	if ( not(username) and nmap.registry.mssqlusers ) then
		-- do we have a sysadmin?
		if ( nmap.registry.mssqlusers.sa ) then
			username = "sa"
			password = nmap.registry.mssqlusers.sa
		else
			-- ok were stuck with some non sysadmin account, just get the first one
			for user, pass in pairs(nmap.registry.mssqlusers) do
				username = user
				password = pass
				break
			end
		end
	end
	
	-- If we don't have a valid username, simply fail silently
	if ( not(username) ) then
		return
	end
	
	helper = mssql.Helper:new()
 	status, response = helper:Connect(host, port)
	if ( not(status) ) then
		return "  \n\n" .. response
	end
		
	status, response = helper:Login( username, password, nil, host.ip )
	if ( not(status) ) then
		return "  \n\nERROR: " .. response
	end

	for _, v in ipairs( queries ) do
		for header, query in pairs(v) do
			status, result_part = helper:Query( query )

			if ( not(status) ) then
				return "  \n\nERROR: " .. result_part
			end
			result_part = mssql.Util.FormatOutputTable( result_part, true )
			result_part.name = header
			table.insert( result, result_part )
		end
	end
	
	helper:Disconnect()
	
	return stdnse.format_output( true, result )

end
