summaryrefslogtreecommitdiffstats
path: root/scripts/voldemort-info.nse
blob: a373ec17686a006ac41e5901a37eb7137c76bc44 (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
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"

description = [[
Retrieves cluster and store information from the Voldemort distributed key-value store using the Voldemort Native Protocol.
]]

---
-- @usage
-- nmap -p 6666 --script voldemort-info <ip>
--
-- @output
-- PORT     STATE SERVICE
-- 6666/tcp open  irc
-- | voldemort-info:
-- |   Cluster
-- |     Name: mycluster
-- |     Id: 0
-- |     Host: localhost
-- |     HTTP Port: 8081
-- |     TCP Port: 6666
-- |     Admin Port: 6667
-- |     Partitions: 0, 1
-- |   Stores
-- |     test
-- |       Persistence: bdb
-- |       Description: Test store
-- |       Owners: harry@hogwarts.edu, hermoine@hogwarts.edu
-- |       Routing strategy: consistent-routing
-- |       Routing: client
-- |     wordcounts
-- |       Persistence: read-only
-- |       Routing strategy: consistent-routing
-- |_      Routing: client
--

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


portrule = shortport.port_or_service(6666, "vp3", "tcp")

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

-- Connect to the server and make sure it supports the vp3 protocol
-- @param host table as received by the action method
-- @param port table as received by the action method
-- @return status true on success, false on failure
-- @return socket connected to the server
local function connect(host, port)
  local socket = nmap.new_socket()
  socket:set_timeout(5000)

  local status, err = socket:connect(host, port)
  if ( not(status) ) then
    return false, "Failed to connect to server"
  end

  status, err = socket:send("vp3")
  if ( not(status) ) then
    return false, "Failed to send request to server"
  end

  local response
  status, response = socket:receive_bytes(2)
  if ( not(status) ) then
    return false, "Failed to receive response from server"
  elseif( response ~= "ok" ) then
    return false, "Unsupported protocol"
  end
  return true, socket
end

-- Get Voldemort metadata
-- @param socket connected to the server
-- @param file the xml file to retrieve
-- @return status true on success false on failure
-- @return data string as received from the server
local function getMetadata(socket, file)

  local req = "\x01\x00" .. string.pack(">s1x I4 s1x", "metadata", 0, file)
  local status, err = socket:send(req)
  if ( not(status) ) then
    return false, "Failed to send request to server"
  end
  local status, data = socket:receive_bytes(10)
  if ( not(status) ) then
    return false, "Failed to receive response from server"
  end
  local len = string.unpack(">I2", data, 9)
  while( #data < len - 2 ) do
    local status, tmp = socket:receive_bytes(len - 2 - #data)
    if ( not(status) ) then
      return false, "Failed to receive response from server"
    end
    data = data .. tmp
  end
  return true, data
end


action = function(host, port)

  -- table of variables to query the server
  local vars = {
    ["cluster"] = {
      { key = "Name", match = "<cluster>.-<name>(.-)</name>" },
      { key = "Id", match = "<cluster>.-<server>.-<id>(%d-)</id>.-</server>" },
      { key = "Host",  match = "<cluster>.-<server>.-<host>(%w-)</host>.-</server>" },
      { key = "HTTP Port", match = "<cluster>.-<server>.-<http%-port>(%d-)</http%-port>.-</server>" },
      { key = "TCP Port", match = "<cluster>.-<server>.-<socket%-port>(%d-)</socket%-port>.-</server>" },
      { key = "Admin Port", match = "<cluster>.-<server>.-<admin%-port>(%d-)</admin%-port>.-</server>" },
      { key = "Partitions", match = "<cluster>.-<server>.-<partitions>([%d%s,]*)</partitions>.-</server>" },
    },
    ["store"] = {
      { key = "Persistence", match = "<store>.-<persistence>(.-)</persistence>" },
      { key = "Description", match = "<store>.-<description>(.-)</description>" },
      { key = "Owners", match = "<store>.-<owners>(.-)</owners>" },
      { key = "Routing strategy", match = "<store>.-<routing%-strategy>(.-)</routing%-strategy>" },
      { key = "Routing", match = "<store>.-<routing>(.-)</routing>" },
    },
  }

  -- connect to the server
  local status, socket = connect(host, port)
  if ( not(status) ) then
    return fail(socket)
  end

  -- get the cluster meta data
  local status, response = getMetadata(socket, "cluster.xml")
  if ( not(status) or not(response:match("<cluster>.*</cluster>")) ) then
    return
  end

  -- Get the cluster details
  local cluster_tbl = { name = "Cluster" }
  for _, item in ipairs(vars["cluster"]) do
    local val = response:match(item.match)
    if ( val ) then
      table.insert(cluster_tbl, ("%s: %s"):format(item.key, val))
    end
  end

  -- get the stores meta data
  local status, response = getMetadata(socket, "stores.xml")
  if ( not(status) or not(response:match("<stores>.-</stores>")) ) then
    return
  end

  local result, stores = {}, { name = "Stores" }
  table.insert(result, cluster_tbl)

  -- iterate over store items
  for store in response:gmatch("<store>.-</store>") do
    local name = store:match("<store>.-<name>(.-)</name>")
    local store_tbl = { name = name or "unknown" }

    for _, item in ipairs(vars["store"]) do
      local val = store:match(item.match)
      if ( val ) then
        table.insert(store_tbl, ("%s: %s"):format(item.key, val))
      end
    end
    table.insert(stores, store_tbl)
  end
  table.insert(result, stores)
  return stdnse.format_output(true, result)
end