summaryrefslogtreecommitdiffstats
path: root/nselib/rtsp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/rtsp.lua')
-rw-r--r--nselib/rtsp.lua291
1 files changed, 291 insertions, 0 deletions
diff --git a/nselib/rtsp.lua b/nselib/rtsp.lua
new file mode 100644
index 0000000..cd97229
--- /dev/null
+++ b/nselib/rtsp.lua
@@ -0,0 +1,291 @@
+---
+-- This Real Time Streaming Protocol (RTSP) library implements only a minimal
+-- subset of the protocol needed by the current scripts.
+--
+-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
+-- @author Patrik Karlsson <patrik@cqure.net>
+--
+-- The library contains the following classes:
+--
+-- * <code>Request</code>
+-- ** This class contains the functions needed to create the RTSP request
+--
+-- * <code>Response</code>
+-- ** This class contains the functions needed to parse the RTSP response
+--
+-- * <code>Client</code>
+-- ** This class contains the RTSP client, a class responsible for sending
+-- and receiving requests and responses to/from the server
+--
+-- * <code>Helper</code>
+-- ** This class serves as the main interface for script writers
+--
+-- The following sample code shows how to use the library:
+-- <code>
+-- local helper = rtsp.Helper:new(host, port)
+-- local status = helper:connect()
+-- local response
+-- status, response = helper:describe(url)
+-- helper:close()
+-- </code>
+
+--
+-- Version 0.1
+-- Created 10/23/2011 - v0.1 - Created by Patrik Karlsson
+--
+
+local nmap = require "nmap"
+local stdnse = require "stdnse"
+local stringaux = require "stringaux"
+local table = require "table"
+_ENV = stdnse.module("rtsp", stdnse.seeall)
+
+-- The RTSP Request object
+Request = {
+
+ --- Creates a new Request instance
+ -- @return o instance of Request
+ new = function(self, url, headers)
+ local o = { url = url, req = {}, headers = headers or {} }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --- Sets the RTSP Request method
+ -- @param method string containing the RTSP method
+ setMethod = function(self, method)
+ self.method = method
+ end,
+
+ --- Sets the RTSP sequence number
+ -- @param cseq number containing the sequence number
+ setCSeq = function(self, cseq)
+ self.cseq = cseq
+ end,
+
+ --- Adds an optional header to the RTSP request
+ -- @param header string containing the header name
+ -- @param value string containing the header value
+ addHeader = function(self, header, value)
+ table.insert( self.headers, { header = value } )
+ end,
+
+ --- Converts the Request to a string
+ --
+ -- @return req string containing the request as a string
+ __tostring = function(self)
+ assert(self.cseq, "Request is missing required header CSeq")
+ assert(self.url, "Request is missing URL")
+
+ local req = {
+ ("%s %s RTSP/1.0"):format(self.method, self.url),
+ ("CSeq: %d"):format(self.cseq),
+ table.unpack(self.headers)
+ }
+ table.insert(req, "")
+ table.insert(req, "")
+ return table.concat(req, "\r\n")
+ end,
+}
+
+-- The RTSP response instance
+Response = {
+
+ --- Creates a new Response instance
+ -- @param data string containing the unparsed data
+ new = function(self, data)
+ assert(data, "No data was supplied")
+ local o = {
+ raw = data,
+ status = tonumber(data:match("^RTSP%/1%.0 (%d*) "))
+ }
+
+ -- Split the response into a temporary array
+ local tmp = stringaux.strsplit("\r\n", data)
+ if ( not(tmp) ) then return nil end
+
+ -- we should have atleast one entry
+ if ( #tmp > 1 ) then
+ o.headers = {}
+ for i=2, #tmp do
+ -- if we have an empty line, this should be the end of headers
+ if ( #tmp[i] == 0 ) then break end
+ local key, val = tmp[i]:match("^(.-): (.*)$")
+ -- create a key per header name
+ o.headers[key] = val
+ end
+ end
+
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+}
+
+
+-- RTSP Client class
+Client = {
+
+ -- Creates a new Client instance
+ -- @param host table as received by the action method
+ -- @param port table as received by the action method
+ -- @return o instance of Client
+ new = function(self, host, port)
+ local o = {
+ host = host,
+ port = port,
+ cseq = 0,
+ headers = { },
+ retries = 3,
+ timeout = 10 * 1000,
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --- Sets the number of retries for socket reads
+ -- @param retries number containing the number of retries
+ setRetries = function(self, retries) self.retries = retries end,
+
+ --- Sets the socket connection timeout in ms
+ -- @param timeout number containing the timeout in ms
+ setTimeout = function(self, timeout) self.timeout = timeout end,
+
+ --- Adds a RTSP header to the request
+ -- @param header string containing the header name
+ -- @param value string containing the header value
+ addHeader = function(self, header, value)
+ table.insert(self.headers, { ("%s: %s"):format(header,value) } )
+ end,
+
+ --- Connects to the RTSP server
+ -- @return status true on success, false on failure
+ -- @return err string containing the error message on failure
+ connect = function(self)
+ self.socket = nmap.new_socket()
+ self.socket:set_timeout(self.timeout)
+ local status = self.socket:connect(self.host, self.port)
+ if ( not(status) ) then
+ stdnse.debug2("Failed to connect to the server: %s", self.host.ip)
+ return false, ("Failed to connect to the server: %s"):format(self.host.ip)
+ end
+ return true
+ end,
+
+ --- Sends a DESCRIBE request to the server and receives the response
+ -- @param url string containing the RTSP URL
+ -- @return status true on success, false on failure
+ -- @return response Response instance on success
+ -- err string containing the error message on failure
+ describe = function(self, url)
+ local req = Request:new(url, self.headers)
+ req:setMethod("DESCRIBE")
+ return self:exch(req)
+ end,
+
+ options = function(self, url)
+ local req = Request:new(url, self.headers)
+ req:setMethod("OPTIONS")
+ return self:exch(req)
+ end,
+
+ --- Sends a request to the server and receives the response and attempts
+ -- to retry if either send or receive fails.
+ -- @param request instance of Request
+ -- @return status true on success, false on failure
+ -- @return response Response instance on success
+ -- err string containing the error message on failure
+ exch = function(self, req)
+ local retries = self.retries
+ local status, data
+ self.cseq = self.cseq + 1
+ req:setCSeq( self.cseq )
+
+ repeat
+ local err
+ status, err = self.socket:send( tostring(req) )
+ -- check if send was successful, in case it wasn't AND
+ -- this is our last retry, ABORT
+ if ( not(status) and 0 == retries - 1 ) then
+ stdnse.debug2("Failed to send request to server (%s)", err)
+ return false, ("Failed to send request to server (%s)"):format(err)
+ -- if send was successful, attempt to receive the response
+ elseif ( status ) then
+ status, data = self.socket:receive()
+ -- if we got the response all right, break out of retry loop
+ if ( status ) then break end
+ end
+ -- if either send or receive fails, re-connect the socket
+ if ( not(status) ) then
+ self:close()
+ local status, err = self:connect()
+ -- if re-connect fails, BAIL out of here
+ if ( not(status) ) then
+ stdnse.debug2("Failed to reconnect socket to server (%s)", err)
+ return false, ("Failed to reconnect socket to server (%s)"):format(err)
+ end
+ end
+ retries = retries - 1
+ until( status or retries == 0 )
+
+ if( not(status) ) then
+ stdnse.debug2("Failed to receive response from server (%s)", data)
+ return false, ("Failed to receive response from server (%s)"):format(data)
+ end
+
+ return true, Response:new(data)
+ end,
+
+ --- Closes the RTSP socket with the server
+ close = function(self)
+ return self.socket:close()
+ end,
+
+}
+
+-- The Helper class is the main script interface
+Helper = {
+
+ -- Creates a new Helper instance
+ -- @param host table as received by the action method
+ -- @param port table as received by the action method
+ -- @return o instance of Client
+ new = function(self, host, port)
+ local o = { host = host, port = port, client = Client:new(host, port) }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Connects to the RTSP server
+ -- @return status true on success, false on failure
+ -- @return err string containing the error message on failure
+ connect = function(self)
+ return self.client:connect()
+ end,
+
+ -- Closes the RTSP socket with the server
+ close = function(self)
+ return self.client:close()
+ end,
+
+ -- Sends a DESCRIBE request to the server and receives the response
+ --
+ -- @param url string containing the RTSP URL
+ -- @return status true on success, false on failure
+ -- @return response string containing the unparsed RTSP response on success
+ -- err string containing the error message on failure
+ describe = function(self, url)
+ return self.client:describe(url)
+ end,
+
+ options = function(self, url)
+ return self.client:options(url)
+ end,
+
+}
+
+return _ENV;