local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local tab = require "tab"

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/tcp open  unknown
-- | memcached-info: 
-- |   Process ID           18568
-- |   Uptime               6950 seconds
-- |   Server time          Sat Dec 31 14:16:10 2011
-- |   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
--

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


-- currently, we only support the TCP, text based protocol
portrule = shortport.port_or_service(11211, "memcached", "tcp")

local filter = {
	
	["pid"] = { name = "Process ID" },
	["uptime"] = { name = "Uptime", func = function(v) return ("%d seconds"):format(v) end },
	["time"] = { name = "Server time", func = stdnse.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 ("\n  ERROR: %s"):format(err) end

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

local function recvResponse(socket)	
	local kvs = {}
	repeat
		local status, response = socket:receive_buf("\r\n", false)
		if ( not(status) ) then
			return false, "Failed to receive response from server"
		end
		local k,v = response:match("^STAT ([^%s]*) (.*)$")
		if ( k and v ) then
			kvs[k] = v
		end
	until ( "END" == response or "ERROR" == response )
	
	return true, kvs
end

action = function(host, port)

	local socket = nmap.new_socket()
	socket:set_timeout(10000)
	local status = socket:connect(host, port)
	if ( not(status) ) then
		return fail("Failed to connect to server")
	end

	status = socket:send("stats\r\n")
	if ( not(status) ) then
		return fail("Failed to send request to server")
	end
	
	local status, kvs = recvResponse(socket)
	if( not(status) ) then
		return fail(kvs)
	end
	
	status = socket:send("stats settings\r\n")
	if ( not(status) ) then
		return fail("Failed to send request to server")
	end
	
	local status, kvs2 = recvResponse(socket)
	if( not(status) ) then
		return fail(kvs2)
	end
	
	kvs = mergetab(kvs, kvs2)
	
	local result = tab.new(2)
	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] )
			tab.addrow(result, name, val)
		end
	end
	return stdnse.format_output(true, tab.dump(result))

end
