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
|