summaryrefslogtreecommitdiffstats
path: root/scripts/memcached-info.nse
blob: a7168803a0e84a43bed055383369c34aea2b7e54 (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
180
181
182
183
184
local os = require "os"
local datetime = require "datetime"
local nmap = require "nmap"
local match = require "match"
local math = require "math"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"

description = [[
Retrieves information (including system architecture, process ID, and
server time) from distributed memory object caching system memcached.
]]

---
-- @usage
-- nmap -p 11211 --script memcached-info
--
-- @output
-- 11211/udp open  unknown
-- | memcached-info:
-- |   Process ID: 18568
-- |   Uptime: 6950 seconds
-- |   Server time: 2018-03-02T03:35:09
-- |   Architecture: 64 bit
-- |   Used CPU (user): 0.172010
-- |   Used CPU (system): 0.200012
-- |   Current connections: 10
-- |   Total connections: 78
-- |   Maximum connections: 1024
-- |   TCP Port: 11211
-- |   UDP Port: 11211
-- |_  Authentication: no
--
-- @xmloutput
-- <elem key="Process ID">17307</elem>
-- <elem key="Uptime">10662 seconds</elem>
-- <elem key="Server time">2018-03-01T16:46:59</elem>
-- <elem key="Architecture">64 bit</elem>
-- <elem key="Used CPU (user)">0.212809</elem>
-- <elem key="Used CPU (system)">0.157151</elem>
-- <elem key="Current connections">5</elem>
-- <elem key="Total connections">11</elem>
-- <elem key="Maximum connections">1024</elem>
-- <elem key="TCP Port">11211</elem>
-- <elem key="UDP Port">11211</elem>
-- <elem key="Authentication">no</elem>

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

portrule = shortport.port_or_service(11211, "memcached", {"tcp", "udp"})

local filter = {

  ["pid"] = { name = "Process ID" },
  ["uptime"] = { name = "Uptime", func = function(v) return ("%d seconds"):format(v) end },
  ["time"] = { name = "Server time", func = datetime.format_timestamp },
  ["pointer_size"] = { name = "Architecture", func = function(v) return v .. " bit" end },
  ["rusage_user"] = { name = "Used CPU (user)" },
  ["rusage_system"] = { name = "Used CPU (system)"},
  ["curr_connections"] = { name = "Current connections"},
  ["total_connections"] = { name = "Total connections"},
  ["maxconns"] = { name = "Maximum connections" },
  ["tcpport"] = { name = "TCP Port" },
  ["udpport"] = { name = "UDP Port" },
  ["auth_enabled_sasl"] = { name = "Authentication" }

}

local order = {
  "pid", "uptime", "time", "pointer_size", "rusage_user", "rusage_system",
  "curr_connections", "total_connections", "maxconns", "tcpport", "udpport",
  "auth_enabled_sasl"
}

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

local function mergetab(tab1, tab2)
  for k, v in pairs(tab2) do
    tab1[k] = v
  end
  return tab1
end

local Comm = {
  new = function(self, host, port, options)
    local o = { host = host, port = port, options = options or {}}
    self.protocol = port.protocol
    self.req_id = math.random(0,0xfff)
    setmetatable(o, self)
    self.__index = self
    return o
  end,
  connect = function(self)
    self.socket = nmap.new_socket(self.protocol)
    self.socket:set_timeout(self.options.timeout or stdnse.get_timeout(self.host))
    return self.socket:connect(self.host, self.port)
  end,
  exchange = function(self, data)
    local req_id = self.req_id
    self.req_id = req_id + 1
    if self.protocol == "udp" then
      data = string.pack(">I2 I2 I2 I2",
        req_id, -- request ID
        0, -- sequence number
        1, -- number of datagrams
        0 -- reserved, must be 0
        ) .. data
    end
    local status = self.socket:send(data)
    if not status then
      return false, "Failed to send request to server"
    end
    if self.protocol == "udp" then
      local msgs = {}
      local dgrams = 0
      repeat
        local status, response = self.socket:receive_bytes(8)
        if not status then return false, "Failed to receive entire response" end
        local resp_id, seq, ndgrams, pos = string.unpack(">I2 I2 I2 xx", response)
        if resp_id == req_id then
          dgrams = ndgrams
          msgs[seq+1] = string.sub(response, pos)
        end
      until #msgs >= dgrams
      return true, table.concat(msgs)
    end

    -- pattern matches ERR or ERROR at the beginning of a string or after a newline
    return self.socket:receive_buf(match.pattern_limit("%f[^\n\0]E[NR][DR]O?R?\r\n", 2048), true)
  end,
}

local function parseResponse(response, expected)
  local kvs = {}
  for k, v in response:gmatch(("%%f[^\n\0]%s ([^%%s]*) (.-)\r\n"):format(expected)) do
    stdnse.debug1("k=%s, v=%s", k, v)
    kvs[k] = v
  end
  return kvs
end

action = function(host, port)

  local client = Comm:new(host, port)
  local status = client:connect()
  if ( not(status) ) then
    return fail("Failed to connect to server")
  end

  local request_time = os.time()
  local status, response = client:exchange("stats\r\n")
  if ( not(status) ) then
    return fail(("Failed to send request to server: %s"):format(response))
  end

  local kvs = parseResponse(response, "STAT")
  if kvs.time then
    datetime.record_skew(host, kvs.time, request_time)
  end

  local status, response = client:exchange("stats settings\r\n")
  if ( not(status) ) then
    return fail(("Failed to send request to server: %s"):format(response))
  end

  local kvs2 = parseResponse(response, "STAT")

  kvs = mergetab(kvs, kvs2)

  local result = stdnse.output_table()
  for _, item in ipairs(order) do
    if ( kvs[item] ) then
      local name = filter[item].name
      local val = ( filter[item].func and filter[item].func(kvs[item]) or kvs[item] )
      result[name] = val
    end
  end
  return result

end