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
|