408 lines
12 KiB
Lua
408 lines
12 KiB
Lua
---
|
|
-- Implementation of the XDMCP (X Display Manager Control Protocol) based on:
|
|
-- x http://www.xfree86.org/current/xdmcp.pdf
|
|
--
|
|
-- @author Patrik Karlsson <patrik@cqure.net>
|
|
|
|
local ipOps = require "ipOps"
|
|
local nmap = require "nmap"
|
|
local stdnse = require "stdnse"
|
|
local string = require "string"
|
|
local table = require "table"
|
|
_ENV = stdnse.module("xdmcp", stdnse.seeall)
|
|
|
|
-- Supported operations
|
|
OpCode = {
|
|
BCAST_QUERY = 1,
|
|
QUERY = 2,
|
|
WILLING = 5,
|
|
REQUEST = 7,
|
|
ACCEPT = 8,
|
|
MANAGE = 10,
|
|
}
|
|
|
|
-- Packet class
|
|
Packet = {
|
|
|
|
-- The cdmcp header
|
|
Header = {
|
|
|
|
-- Creates a new instance of class
|
|
-- @param version number containing the protocol version
|
|
-- @param opcode number containing the opcode type
|
|
-- @param length number containing the length of the data
|
|
-- @return o instance of class
|
|
new = function(self, version, opcode, length)
|
|
local o = { version = version, opcode = opcode, length = length }
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
-- Parses data based on which a new object is instantiated
|
|
-- @param data opaque string containing data received over the wire
|
|
-- @return hdr instance of class
|
|
-- @return pos position in the string where parsing left off
|
|
parse = function(data)
|
|
local hdr = Packet.Header:new()
|
|
local pos
|
|
hdr.version, hdr.opcode, hdr.length, pos = string.unpack(">I2I2I2", data)
|
|
return hdr, pos
|
|
end,
|
|
|
|
-- Converts the instance to an opaque string
|
|
-- @return str string containing the instance
|
|
__tostring = function(self)
|
|
assert(self.length, "No header length was supplied")
|
|
return string.pack(">I2I2I2", self.version, self.opcode, self.length)
|
|
end,
|
|
},
|
|
|
|
[OpCode.QUERY] = {
|
|
|
|
-- Creates a new instance of class
|
|
-- @param authnames table of strings containing authentication
|
|
-- mechanism names.
|
|
-- @return o instance of class
|
|
new = function(self, authnames)
|
|
local o = {
|
|
header = Packet.Header:new(1, OpCode.QUERY),
|
|
authnames = authnames or {},
|
|
}
|
|
o.header.length = #o.authnames + 1
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
-- Converts the instance to an opaque string
|
|
-- @return str string containing the instance
|
|
__tostring = function(self)
|
|
local data = {
|
|
tostring(self.header),
|
|
string.pack("B", #self.authnames),
|
|
}
|
|
for _, name in ipairs(self.authnames) do
|
|
data[#data+1] = string.pack(">s2", name)
|
|
end
|
|
return table.concat(data)
|
|
end,
|
|
|
|
},
|
|
|
|
[OpCode.BCAST_QUERY] = {
|
|
new = function(...)
|
|
local packet = Packet[OpCode.QUERY]:new(...)
|
|
packet.header.opcode = OpCode.BCAST_QUERY
|
|
return packet
|
|
end,
|
|
|
|
__tostring = function(...)
|
|
return Packet[OpCode.QUERY]:__tostring(...)
|
|
end
|
|
|
|
},
|
|
|
|
[OpCode.WILLING] = {
|
|
|
|
-- Creates a new instance of class
|
|
-- @return o instance of class
|
|
new = function(self)
|
|
local o = {
|
|
header = Packet.Header:new(1, OpCode.WILLING)
|
|
}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
-- Parses data based on which a new object is instantiated
|
|
-- @param data opaque string containing data received over the wire
|
|
-- @return hdr instance of class
|
|
parse = function(data)
|
|
local willing = Packet[OpCode.WILLING]:new()
|
|
local pos
|
|
willing.header, pos = Packet.Header.parse(data)
|
|
|
|
willing.authname, willing.hostname,
|
|
willing.status, pos = string.unpack("s1s1s1", data, pos)
|
|
return willing
|
|
end,
|
|
|
|
},
|
|
|
|
[OpCode.REQUEST] = {
|
|
|
|
-- The connection class
|
|
Connection = {
|
|
|
|
IpType = {
|
|
IPv4 = 0,
|
|
IPv6 = 6,
|
|
},
|
|
|
|
-- Creates a new instance of class
|
|
-- @param iptype number
|
|
-- @param ip opaque string containing the ip
|
|
-- @return o instance of class
|
|
new = function(self, iptype, ip)
|
|
local o = {
|
|
iptype = iptype,
|
|
ip = ip,
|
|
}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
},
|
|
|
|
-- Creates a new instance of class
|
|
-- @param disp_no number containing the display name
|
|
-- @param auth_name string containing the authentication name
|
|
-- @param auth_data string containing additional authentication data
|
|
-- @param authr_names string containing authorization mechanisms
|
|
-- @param manf_id string containing the manufacturer id
|
|
-- @return o instance of class
|
|
new = function(self, disp_no, conns, auth_name, auth_data, authr_names, manf_id )
|
|
local o = {
|
|
header = Packet.Header:new(1, OpCode.REQUEST),
|
|
disp_no = disp_no or 1,
|
|
conns = conns or {},
|
|
auth_name = auth_name or "",
|
|
auth_data = auth_data or "",
|
|
authr_names = authr_names or {},
|
|
manf_id = manf_id or "",
|
|
}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
-- Adds a new connection entry
|
|
-- @param conn instance of Connections
|
|
addConnection = function(self, conn)
|
|
table.insert(self.conns, conn)
|
|
end,
|
|
|
|
-- Adds a new authorization entry
|
|
-- @param str string containing the name of the authorization mechanism
|
|
addAuthrName = function(self, str)
|
|
table.insert(self.authr_names, str)
|
|
end,
|
|
|
|
-- Converts the instance to an opaque string
|
|
-- @return str string containing the instance
|
|
__tostring = function(self)
|
|
local data = {
|
|
string.pack(">I2B", self.disp_no, #self.conns),
|
|
}
|
|
for _, conn in ipairs(self.conns) do
|
|
data[#data+1] = string.pack(">I2", conn.iptype)
|
|
end
|
|
data[#data+1] = string.pack("B", #self.conns)
|
|
for _, conn in ipairs(self.conns) do
|
|
data[#data+1] = string.pack(">s2", ipOps.ip_to_str(conn.ip))
|
|
end
|
|
data[#data+1] = string.pack(">s2s2B", self.auth_name, self.auth_data, #self.authr_names)
|
|
for _, authr in ipairs(self.authr_names) do
|
|
data[#data+1] = string.pack(">s2", authr)
|
|
end
|
|
data[#data+1] = string.pack(">s2", self.manf_id)
|
|
data = table.concat(data)
|
|
self.header.length = #data
|
|
|
|
return tostring(self.header) .. data
|
|
end,
|
|
|
|
},
|
|
|
|
[OpCode.ACCEPT] = {
|
|
|
|
-- Creates a new instance of class
|
|
-- @param session_id number containing the session id
|
|
-- @param auth_name string containing the authentication name
|
|
-- @param auth_data string containing additional authentication data
|
|
-- @param authr_name string containing the authorization mechanism name
|
|
-- @param authr_names string containing authorization mechanisms
|
|
-- @return o instance of class
|
|
new = function(self, session_id, auth_name, auth_data, authr_name, authr_data)
|
|
local o = {
|
|
header = Packet.Header:new(1, OpCode.ACCEPT),
|
|
session_id = session_id,
|
|
auth_name = auth_name,
|
|
auth_data = auth_data,
|
|
authr_name = authr_name,
|
|
authr_data = authr_data,
|
|
}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
-- Parses data based on which a new object is instantiated
|
|
-- @param data opaque string containing data received over the wire
|
|
-- @return hdr instance of class
|
|
parse = function(data)
|
|
local accept = Packet[OpCode.ACCEPT]:new()
|
|
local pos
|
|
accept.header, pos = Packet.Header.parse(data)
|
|
accept.session_id, accept.auth_name, accept.auth_data,
|
|
accept.authr_name, accept.authr_data, pos = string.unpack(">I4s2s2s2s2", data, pos)
|
|
return accept
|
|
end,
|
|
|
|
},
|
|
|
|
[OpCode.MANAGE] = {
|
|
|
|
-- Creates a new instance of class
|
|
-- @param session_id number containing the session id
|
|
-- @param disp_no number containing the display number
|
|
-- @param disp_class string containing the display class
|
|
-- @return o instance of class
|
|
new = function(self, sess_id, disp_no, disp_class)
|
|
local o = {
|
|
header = Packet.Header:new(1, OpCode.MANAGE),
|
|
session_id = sess_id,
|
|
disp_no = disp_no,
|
|
disp_class = disp_class or ""
|
|
}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
-- Converts the instance to an opaque string
|
|
-- @return str string containing the instance
|
|
__tostring = function(self)
|
|
local data = string.pack(">I4I2s2", self.session_id, self.disp_no, self.disp_class)
|
|
self.header.length = #data
|
|
return tostring(self.header) .. data
|
|
end,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
-- The Helper class serves as the main script interface
|
|
Helper = {
|
|
|
|
-- Creates a new instance of Helper
|
|
-- @param host table as received by the action method
|
|
-- @param port table as received by the action method
|
|
-- @param options table
|
|
-- @return o new instance of Helper
|
|
new = function(self, host, port, options)
|
|
local o = {
|
|
host = host,
|
|
port = port,
|
|
options = options or {},
|
|
}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
-- "Connects" to the server (ie. creates the socket)
|
|
-- @return status, true on success, false on failure
|
|
connect = function(self)
|
|
self.socket = nmap.new_socket("udp")
|
|
self.socket:set_timeout(self.options.timeout or 10000)
|
|
return true
|
|
end,
|
|
|
|
-- Creates a xdmcp session
|
|
-- @param auth_name string containing the authentication name
|
|
-- @param authr_name string containing the authorization mechanism name
|
|
-- @param disp_class string containing the display class
|
|
-- @return status true on success, false on failure
|
|
-- @return response table or err string containing an error message
|
|
createSession = function(self, auth_names, authr_names, disp_no)
|
|
local info = nmap.get_interface_info(self.host.interface)
|
|
if ( not(info) ) then
|
|
return false, ("Failed to get information for interface %s"):format(self.host.interface)
|
|
end
|
|
|
|
local req = Packet[OpCode.QUERY]:new(auth_names)
|
|
local status, response = self:exch(req)
|
|
if ( not(status) ) then
|
|
return false, response
|
|
elseif ( response.header.opcode ~= OpCode.WILLING ) then
|
|
return false, "Received unexpected response"
|
|
end
|
|
|
|
local REQ = Packet[OpCode.REQUEST]
|
|
local iptype = REQ.Connection.IpType.IPv4
|
|
if ( nmap.address_family() == 'inet6' ) then
|
|
iptype = REQ.Connection.IpType.IPv6
|
|
end
|
|
|
|
local conns = { REQ.Connection:new(iptype, info.address) }
|
|
local req = REQ:new(disp_no, conns, nil, nil, authr_names)
|
|
local status, response = self:exch(req)
|
|
if ( not(status) ) then
|
|
return false, response
|
|
elseif ( response.header.opcode ~= OpCode.ACCEPT ) then
|
|
return false, "Received unexpected response"
|
|
end
|
|
|
|
-- Sending this last manage packet doesn't make any sense as we can't
|
|
-- set up a listening TCP server anyway. When we can, we could enable
|
|
-- this and wait for the incoming request and retrieve X protocol info.
|
|
|
|
-- local manage = Packet[OpCode.MANAGE]:new(response.session_id,
|
|
-- disp_no, "MIT-unspecified")
|
|
-- local status, response = self:exch(manage)
|
|
-- if ( not(status) ) then
|
|
-- return false, response
|
|
-- end
|
|
|
|
return true, {
|
|
session_id = response.session_id,
|
|
auth_name = response.auth_name,
|
|
auth_data = response.auth_data,
|
|
authr_name = response.authr_name,
|
|
authr_data = response.authr_data,
|
|
}
|
|
end,
|
|
|
|
send = function(self, req)
|
|
return self.socket:sendto(self.host, self.port, tostring(req))
|
|
end,
|
|
|
|
recv = function(self)
|
|
local status, data = self.socket:receive()
|
|
if ( not(status) ) then
|
|
return false, data
|
|
end
|
|
local header = Packet.Header.parse(data)
|
|
if ( not(header) ) then
|
|
return false, "Failed to parse xdmcp header"
|
|
end
|
|
if ( not(Packet[header.opcode]) ) then
|
|
return false, ("No parser for opcode: %d"):format(header.opcode)
|
|
end
|
|
local resp = Packet[header.opcode].parse(data)
|
|
if ( not(resp) ) then
|
|
return false, "Failed to parse response"
|
|
end
|
|
return true, resp
|
|
end,
|
|
|
|
-- Sends a request to the server, receives and parses a response
|
|
-- @param req instance of Packet
|
|
-- @return status true on success, false on failure
|
|
-- @return response instance of response packet
|
|
exch = function(self, req)
|
|
local status, err = self:send(req)
|
|
if ( not(status) ) then
|
|
return false, "Failed to send xdmcp request"
|
|
end
|
|
return self:recv()
|
|
end,
|
|
|
|
}
|
|
|
|
return _ENV;
|