summaryrefslogtreecommitdiffstats
path: root/scripts/maxdb-info.nse
blob: 50d375a800294439d714594b6e9beb49be128a9e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local stringaux = require "stringaux"
local tab = require "tab"
local table = require "table"

description = [[
Retrieves version and database information from a SAP Max DB database.
]]

---
-- @usage
-- nmap -p 7210 --script maxdb-info <ip>
--
-- @output
-- PORT     STATE SERVICE REASON
-- 7210/tcp open  maxdb   syn-ack
-- | maxdb-info:
-- |   Version: 7.8.02
-- |   Build: DBMServer 7.8.02   Build 021-121-242-175
-- |   OS: UNIX
-- |   Instroot: /opt/sdb/MaxDB
-- |   Sysname: Linux 3.0.0-12-generic #20-Ubuntu SMP Fri Oct 7 14:56:25 UTC 2011
-- |   Databases
-- |     instance  path            version    kernel  state
-- |     MAXDB     /opt/sdb/MaxDB  7.8.02.21  fast    running
-- |     MAXDB     /opt/sdb/MaxDB  7.8.02.21  quick   offline
-- |     MAXDB     /opt/sdb/MaxDB  7.8.02.21  slow    offline
-- |_    MAXDB     /opt/sdb/MaxDB  7.8.02.21  test    offline
--

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


portrule = shortport.version_port_or_service(7210, "maxdb", "tcp")

-- Sends and receive a MaxDB packet
-- @param socket already connected to the server
-- @param packet string containing the data to send
-- @return status true on success, false on failure
-- @return data string containing the raw response from the server
local function exchPacket(socket, packet)
  local status, err = socket:send(packet)
  if ( not(status) ) then
    stdnse.debug2("Failed to send packet to server")
    return false, "Failed to send packet to server"
  end

  local data
  status, data= socket:receive()
  if ( not(status) ) then
    stdnse.debug2("Failed to read packet from server")
    return false, "Failed to read packet from server"
  end
  local len = string.unpack("<I2", data)

  -- make sure we've got it all
  if ( len ~= #data ) then
    local tmp
    status, tmp = socket:receive_bytes(len - #data)
    if ( not(status) ) then
      stdnse.debug2("Failed to read packet from server")
      return false, "Failed to read packet from server"
    end
    data = data .. tmp
  end
  return true, data
end

-- Sends and receives a MaxDB command and does some very basic checks of the
-- response.
-- @param socket already connected to the server
-- @param packet string containing the data to send
-- @return status true on success, false on failure
-- @return data string containing the raw response from the server
local function exchCommand(socket, packet)
  local status, data = exchPacket(socket, packet)
  if( status ) then
    if ( #data < 26 ) then
      return false, "Response to short"
    end
    if ( "OK" ~= data:sub(25, 26) ) then
      return false, "Incorrect response from server (no OK found)"
    end
  end
  return status, data
end

-- Parses and decodes the raw version response from the server
-- @param data string containing the raw response
-- @return version_info table containing a number of dynamic fields based on
--         the response from the server. The fields typically include:
--         <code>VERSION</code>, <code>BUILD</code>, <code>OS</code>,
--         <code>INSTROOT</code>,<code>LOGON</code>, <code>CODE</code>,
--         <code>SWAP</code>, <code>UNICODE</code>, <code>INSTANCE</code>,
--         <code>SYSNAME</code>, <code>MASKING</code>,
--         <code>REPLYTREATMENT</code> and <code>SDBDBM_IPCLOCATION</code>
local function parseVersion(data)
  local version_info = {}
  if ( #data > 27 ) then
    for _, line in ipairs(stringaux.strsplit("\n", data:sub(28))) do
      local key, val = line:match("^(%S+)%s-=%s(.*)%s*$")
      if ( key ) then  version_info[key] = val end
    end
  end
  return version_info
end

-- Parses and decodes the raw database response from the server
-- @param data string containing the raw response
-- @return result string containing a table of database instance information
local function parseDatabases(data)
  local result = tab.new(5)
  tab.addrow(result, "instance", "path", "version", "kernel", "state")
  for _, line in ipairs(stringaux.strsplit("\n", data:sub(28))) do
    local cols = {}
    cols.instance, cols.path, cols.ver, cols.kernel,
      cols.state = line:match("^(.-)%s*\t(.-)%s*\t(.-)%s*\t(.-)%s-\t(.-)%s-$")
    if ( cols.instance ) then
      tab.addrow(result, cols.instance, cols.path, cols.ver, cols.kernel, cols.state)
    end
  end
  return tab.dump(result)
end

local function fail (err) return stdnse.format_output(false, err) end

action = function(host, port)
  -- this could really be more elegant, but it has to do for now
  local handshake   = "5a000000035b000001000000ffffffff000004005a000000000242000409000000400000d03f00000040000070000000000500000004000000020000000300000749343231360004501c2a035201037201097064626d73727600"
  local dbm_version = "28000000033f000001000000ac130000000004002800000064626d5f76657273696f6e2020202020"
  local db_enum     = "20000000033f000001000000ac130000000004002000000064625f656e756d20"

  local socket = nmap.new_socket()
  socket:set_timeout(10000)
  local status, err = socket:connect(host, port)
  local data

  status, data = exchPacket(socket, stdnse.fromhex( handshake))
  if ( not(status) ) then
    return fail("Failed to perform handshake with MaxDB server")
  end

  status, data = exchPacket(socket, stdnse.fromhex( dbm_version))
  if ( not(status) ) then
    return fail("Failed to request version information from server")
  end

  local version_info = parseVersion(data)
  if ( not(version_info) ) then
    return fail("Failed to parse version information from server")
  end

  local result, filter = {}, {"Version", "Build", "OS", "Instroot", "Sysname"}
  for _, f in ipairs(filter) do
    table.insert(result, ("%s: %s"):format(f, version_info[f:upper()]))
  end

  status, data = exchCommand(socket, stdnse.fromhex( db_enum))
  socket:close()
  if ( not(status) ) then
    return fail("Failed to request version information from server")
  end
  local dbs = parseDatabases(data)
  table.insert(result, { name = "Databases", dbs } )

  -- set the version information
  port.version.name = "maxdb"
  port.version.product = "SAP MaxDB"
  port.version.version = version_info.VERSION
  port.version.ostype = version_info.SYSNAME
  nmap.set_port_version(host, port)

  return stdnse.format_output(true, result)
end