summaryrefslogtreecommitdiffstats
path: root/nselib/cassandra.lua
blob: 4ac3390f81975aa5ca0610725c41b020dd0db8e4 (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
185
186
187
188
189
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;