diff options
Diffstat (limited to 'nselib/cassandra.lua')
-rw-r--r-- | nselib/cassandra.lua | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/nselib/cassandra.lua b/nselib/cassandra.lua new file mode 100644 index 0000000..4ac3390 --- /dev/null +++ b/nselib/cassandra.lua @@ -0,0 +1,190 @@ +--- +-- 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: %s", 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: %s", 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 %s", combo) + return false, "Failed to connect to server" + end + + status, err = socket:send(loginstr) + if ( not(status) ) then + stdnse.debug3("Sent packet for %s", combo) + return false, err + end + + local response + status, response = socket:receive_bytes(22) + if ( not(status) ) then + stdnse.debug3("Receive packet for %s", 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 %s", 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; |