191 lines
5.5 KiB
Lua
191 lines
5.5 KiB
Lua
---
|
|
-- Library methods for handling Cassandra Thrift communication as client
|
|
--
|
|
-- @author Vlatko Kosturjak
|
|
-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
|
|
--
|
|
-- Version 0.1
|
|
--
|
|
|
|
local stdnse = require "stdnse"
|
|
local string = require "string"
|
|
_ENV = stdnse.module("cassandra", stdnse.seeall)
|
|
|
|
--[[
|
|
|
|
Cassandra Thrift protocol implementation.
|
|
|
|
For more information about Cassandra, see:
|
|
|
|
http://cassandra.apache.org/
|
|
|
|
]]--
|
|
|
|
-- Protocol magic strings
|
|
CASSANDRAREQ = "\x80\x01\x00\x01"
|
|
CASSANDRARESP = "\x80\x01\x00\x02"
|
|
CASSLOGINMAGIC = "\x00\x00\x00\x01\x0c\x00\x01\x0d\x00\x01\x0b\x0b\x00\x00\x00\x02"
|
|
LOGINSUCC = "\x00\x00\x00\x01\x00"
|
|
LOGINFAIL = "\x00\x00\x00\x01\x0b"
|
|
LOGINACC = "\x00\x00\x00\x01\x0c"
|
|
|
|
--Returns string in cassandra format for login
|
|
--@param username to put in format
|
|
--@param password to put in format
|
|
--@return str : string in cassandra format for login
|
|
function loginstr (username, password)
|
|
return CASSANDRAREQ
|
|
.. string.pack(">s4", "login")
|
|
.. CASSLOGINMAGIC
|
|
.. string.pack(">s4s4s4s4", "username", username, "password", password)
|
|
.. "\x00\x00" -- add two null on the end
|
|
end
|
|
|
|
--Invokes command over socket and returns the response
|
|
--@param socket to connect to
|
|
--@param command to invoke
|
|
--@param cnt is protocol count
|
|
--@return status : true if ok; false if bad
|
|
--@return result : value if status ok, error msg if bad
|
|
function cmdstr (command,cnt)
|
|
return CASSANDRAREQ
|
|
.. string.pack(">s4I4", command, cnt)
|
|
.. "\x00" -- add null on the end
|
|
end
|
|
|
|
--Invokes command over socket and returns the response
|
|
--@param socket to connect to
|
|
--@param command to invoke
|
|
--@param cnt is protocol count
|
|
--@return status : true if ok; false if bad
|
|
--@return result : value if status ok, error msg if bad
|
|
function sendcmd (socket, command, cnt)
|
|
local cmdstr = cmdstr (command,cnt)
|
|
local response
|
|
|
|
local status, err = socket:send(string.pack(">I4", #cmdstr))
|
|
if ( not(status) ) then
|
|
return false, "error sending packet length"
|
|
end
|
|
|
|
status, err = socket:send(cmdstr)
|
|
if ( not(status) ) then
|
|
return false, "error sending packet payload"
|
|
end
|
|
|
|
status, response = socket:receive_bytes(4)
|
|
if ( not(status) ) then
|
|
return false, "error receiving length"
|
|
end
|
|
local size = string.unpack(">I4", response)
|
|
|
|
if #response < size + 4 then
|
|
local resp2
|
|
status, resp2 = socket:receive_bytes(size + 4 - #response)
|
|
if ( not(status) ) then
|
|
return false, "error receiving payload"
|
|
end
|
|
response = response .. resp2
|
|
end
|
|
|
|
-- magic response starts at 5th byte for 4 bytes, 4 byte for length + length of string command
|
|
if response:sub(5, 8 + 4 + #command) ~= CASSANDRARESP .. string.pack(">s4", command) then
|
|
return false, "protocol response error"
|
|
end
|
|
|
|
return true, response
|
|
end
|
|
|
|
--Return Cluster Name
|
|
--@param socket to connect to
|
|
--@param cnt is protocol count
|
|
--@return status : true if ok; false if bad
|
|
--@return result : value if status ok, error msg if bad
|
|
function describe_cluster_name (socket,cnt)
|
|
local cname = "describe_cluster_name"
|
|
local status,resp = sendcmd(socket,cname,cnt)
|
|
|
|
if (not(status)) then
|
|
stdnse.debug1("sendcmd"..resp)
|
|
return false, "error in communication"
|
|
end
|
|
|
|
-- grab the size
|
|
-- pktlen(4) + CASSANDRARESP(4) + lencmd(4) + lencmd(v) + params(7) + next byte position
|
|
local position = 12 + #cname + 7 + 1
|
|
local value = string.unpack(">s4", resp, position)
|
|
return true, value
|
|
end
|
|
|
|
--Return API version
|
|
--@param socket to connect to
|
|
--@param cnt is protocol count
|
|
--@return status : true if ok; false if bad
|
|
--@return result : value if status ok, error msg if bad
|
|
function describe_version (socket,cnt)
|
|
local cname = "describe_version"
|
|
local status,resp = sendcmd(socket,cname,cnt)
|
|
|
|
if (not(status)) then
|
|
stdnse.debug1("sendcmd"..resp)
|
|
return false, "error in communication"
|
|
end
|
|
|
|
-- grab the size
|
|
-- pktlen(4) + CASSANDRARESP(4) + lencmd(4) + lencmd(v) + params(7) + next byte position
|
|
local position = 12 + #cname + 7 + 1
|
|
local value = string.unpack(">s4", resp, position)
|
|
return true, value
|
|
end
|
|
|
|
--Login to Cassandra
|
|
--@param socket to connect to
|
|
--@param username to connect to
|
|
--@param password to connect to
|
|
--@return status : true if ok; false if bad
|
|
--@return result : table of status ok, error msg if bad
|
|
--@return if status ok : remaining data read from socket but not used
|
|
function login (socket,username,password)
|
|
local loginstr = loginstr (username, password)
|
|
local combo = username..":"..password
|
|
|
|
local status, err = socket:send(string.pack(">I4", #loginstr))
|
|
if ( not(status) ) then
|
|
stdnse.debug3("cannot send len "..combo)
|
|
return false, "Failed to connect to server"
|
|
end
|
|
|
|
status, err = socket:send(loginstr)
|
|
if ( not(status) ) then
|
|
stdnse.debug3("Sent packet for "..combo)
|
|
return false, err
|
|
end
|
|
|
|
local response
|
|
status, response = socket:receive_bytes(22)
|
|
if ( not(status) ) then
|
|
stdnse.debug3("Receive packet for "..combo)
|
|
return false, err
|
|
end
|
|
local size = string.unpack(">I4", response)
|
|
|
|
local loginresp = string.sub(response,5,17)
|
|
if (loginresp ~= CASSANDRARESP .. string.pack(">s4", "login")) then
|
|
return false, "protocol error"
|
|
end
|
|
|
|
local magic = string.sub(response,18,22)
|
|
stdnse.debug3("packet for "..combo)
|
|
stdnse.debug3("packet hex: %s", stdnse.tohex(response) )
|
|
stdnse.debug3("size packet hex: %s", stdnse.tohex(size) )
|
|
stdnse.debug3("magic packet hex: %s", stdnse.tohex(magic) )
|
|
|
|
if (magic == LOGINSUCC) then
|
|
return true
|
|
else
|
|
return false, "Login failed."
|
|
end
|
|
end
|
|
|
|
return _ENV;
|