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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
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;
|