1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-24 00:19:01 +00:00

Structured XML output for xmpp-info

This involves some reordering and renaming of fields from the previous
output, but hopefully more readable (less vertical whitespace). This
commit depends on the changes to stdnse.output_table to ignore assigning
new keys to nil and to use the __call metamethod to mean has_contents()
This commit is contained in:
dmiller
2013-05-06 18:39:55 +00:00
parent 930bc91359
commit 3abd1232da

View File

@@ -213,34 +213,22 @@ local connect_tls = function(s, xmlns, server_name)
end
end
local caps = {{}, {}}
local err = {{}, {}}
local features = {{}, {}}
local features_list = {{}, {}}
local mechanisms = {{}, {}}
local methods = {{}, {}}
local tag_stack = {{}, {}}
local t_xmpp = {{}, {}}
local unknown = {}
local scan = function(host, port, server_name, tls, n)
local scan = function(host, port, server_name, tls)
local data, status
local client = nmap.new_socket()
local tls_text
local result = {}
local stream_id
-- Looks like 10 seconds is enough for non RFC-compliant servers...
client:set_timeout(10 * 1000);
caps[n] = {}
err[n] = {}
features[n] = {}
features_list[n] = {}
mechanisms[n] = {}
methods[n] = {}
tag_stack[n] = {}
t_xmpp[n] = {}
local caps = stdnse.output_table()
local err = {}
local features_list = {}
local mechanisms = {}
local methods = {}
local unknown = {}
local t_xmpp = stdnse.output_table()
local xmlns
stdnse.print_debug(port.version.name)
@@ -285,7 +273,7 @@ local scan = function(host, port, server_name, tls, n)
while true do
local tag = receive_tag(client)
if not tag then
table.insert(err[n], "(timeout)")
table.insert(err, "(timeout)")
break
end
log_tag(tag)
@@ -295,30 +283,30 @@ local scan = function(host, port, server_name, tls, n)
if inside() and not known_features[tag.name] then
stdnse.print_debug(tag.name)
unknown[tag.name] = true
table.insert(unknown, tag.name)
end
if tag.name == "stream:stream" and tag.start then
--http://xmpp.org/extensions/xep-0198.html#ns
if tag.attrs['xmlns:ack'] and
tag.attrs['xmlns:ack'] == 'http://www.xmpp.org/extensions/xep-0198.html#ns' then
table.insert(t_xmpp[n], "Stream Management")
table.insert(t_xmpp, "Stream Management")
end
if tag.attrs['xml:lang'] then
table.insert(t_xmpp[n], { name = "Lang", tag.attrs['xml:lang']})
t_xmpp["lang"] = tag.attrs['xml:lang']
end
if tag.attrs.from and tag.attrs.from ~= server_name then
table.insert(t_xmpp[n], { name = "Server name", tag.attrs.from})
t_xmpp["server name"] = tag.attrs.from
end
stream_id = tag.attrs.id
if tag.attrs.version then
table.insert(t_xmpp[n], 'v' .. tag.attrs.version)
t_xmpp["version"] = tag.attrs.version
else
-- Alarm! Not an RFC-compliant server...
-- sample: chirimoyas.es
table.insert(t_xmpp[n], "(no version)")
t_xmpp["version"] = "(none)"
end
end
@@ -327,52 +315,52 @@ local scan = function(host, port, server_name, tls, n)
--http://xmpp.org/extensions/xep-0198.html
--sample: el-tramo.be
local version = string.match(tag.attrs.xmlns, "^urn:xmpp:sm:(%.)")
table.insert(features_list[n], 'Stream management v' .. version)
table.insert(features_list, 'Stream management v' .. version)
end
if tag.name == "starttls" and inside() then
is_starttls = true
elseif tag.name == "address" and tag.finish and inside() then
--http://delta.affinix.com/specs/xmppstream.html
table.insert(features_list[n], "MY IP: " .. tag.contents )
table.insert(features_list, "MY IP: " .. tag.contents )
elseif tag.name == "ver" and inside() then
--http://xmpp.org/extensions/xep-0237.html
table.insert(features_list[n], "Roster Versioning")
table.insert(features_list, "Roster Versioning")
elseif tag.name == "dialback" and inside() then
--http://xmpp.org/extensions/xep-0220.html
table.insert(features_list[n], "Server Dialback")
table.insert(features_list, "Server Dialback")
elseif tag.name == "session" and inside() then
--http://www.ietf.org/rfc/rfc3921.txt
table.insert(features_list[n], "IM Session Establishment")
table.insert(features_list, "IM Session Establishment")
elseif tag.name == "bind" and inside() then
--http://www.ietf.org/rfc/rfc3920.txt
table.insert(features_list[n], "Resource Binding")
table.insert(features_list, "Resource Binding")
elseif tag.name == "amp" and inside() then
--http://xmpp.org/extensions/xep-0079.html
table.insert(features_list[n], "Advanced Message Processing")
table.insert(features_list, "Advanced Message Processing")
elseif tag.name == "register" and inside() then
--http://xmpp.org/extensions/xep-0077.html
--sample: jabber.ru
table.insert(features_list[n], "In-Band Registration")
table.insert(features_list, "In-Band Registration")
elseif tag.name == "auth" and inside() then
--http://xmpp.org/extensions/xep-0078.html
table.insert(mechanisms[n], "Non-SASL")
table.insert(mechanisms, "Non-SASL")
elseif tag.name == "required" and inside('starttls') then
tls_required = true
elseif tag.name == "method" and inside('compression', 'method') then
--http://xmpp.org/extensions/xep-0138.html
if tag.finish then
table.insert(methods[n], tag.contents)
table.insert(methods, tag.contents)
end
elseif tag.name == "mechanism" and inside('mechanisms', 'mechanism') then
if tag.finish then
table.insert(mechanisms[n], tag.contents)
table.insert(mechanisms, tag.contents)
end
elseif tag.name == "c" and inside() then
--http://xmpp.org/extensions/xep-0115.html
--sample: jabber.ru
if tag.attrs and tag.attrs.node then
table.insert(caps[n], { name = "node", tag.attrs.node})
caps["node"] = tag.attrs.node
-- It is a table of well-known node values of "c" tag
-- If it matched then the server software is determined
@@ -392,7 +380,7 @@ local scan = function(host, port, server_name, tls, n)
-- Funny situation: we have a hash of server capabilities list,
-- but we cannot explicitly ask him about the list because we have no name before the authentication.
-- The ugly solution is checking the hash against the most popular capability sets...
table.insert(caps[n], { name = "ver", tag.attrs.ver})
caps["ver"] = tag.attrs.ver
end
end
@@ -401,7 +389,7 @@ local scan = function(host, port, server_name, tls, n)
in_error = tag.start
elseif not got_text then -- non-RFC compliant server!
if tag.contents ~= "" then
table.insert(err[n], { name = "text", tag.contents })
table.insert(err, {text= tag.contents})
end
in_error = false
end
@@ -409,10 +397,10 @@ local scan = function(host, port, server_name, tls, n)
if tag.name == "text" then
if tag.finish then
got_text = true
table.insert(err[n], { name = "text", tag.contents })
table.insert(err, {text= tag.contents})
end
else
table.insert(err[n], tag.name)
table.insert(err, tag.name)
end
end
@@ -425,13 +413,22 @@ local scan = function(host, port, server_name, tls, n)
if is_starttls then
if tls_required then
table.insert(features_list[n], "TLS (required)")
table.insert(features_list, "TLS (required)")
else
table.insert(features_list[n], "TLS")
table.insert(features_list, "TLS")
end
end
return stream_id
return {
stream_id=stream_id,
xmpp=t_xmpp,
features=features_list,
capabilities=caps,
compression_methods=methods,
auth_mechanisms=mechanisms,
errors=err,
unknown=unknown,
}
end
local server_info = function(host, port, id1, id2)
@@ -450,122 +447,108 @@ local server_info = function(host, port, id1, id2)
end
end
local copy_table = function(to, from)
for _,f in pairs(from) do table.insert(to, f) end
end
--Some stuff to transform two tables into one
local cmp = function(a, b)
if type(a) == "table" then
return a['name'] == b['name'] and a[1] == b[1]
else
return a == b
end
end
local get_10_ = function(arr, s)
local r = {}
for _,f in ipairs(arr[1]) do
local seen = 0
for _,ff in ipairs(arr[2]) do
if cmp(f, ff) then seen = 1 end
local factor = function( t1, t2 )
local both = stdnse.output_table()
local t1only = stdnse.output_table()
local t2only = stdnse.output_table()
--ordered key-value categories
for _, cat in ipairs({"xmpp", "capabilities"}) do
local both_c = stdnse.output_table()
local t1only_c = stdnse.output_table()
local t2only_c = stdnse.output_table()
local t1c = t1[cat]
local t2c = t2[cat]
for k,v in pairs(t1c) do
if t2c[k] then
if t2c[k] == v then
both_c[k] = v
else
t1only_c[k] = v
t2only_c[k] = t2c[k]
end
if seen == s then table.insert(r, f) end
else
t1only_c[k] = v
end
end
return r
end
local get_10 = function(arr) return get_10_(arr, 0) end
local get_01 = function(arr) return get_10({ arr[2], arr[1] }) end
local get_11 = function(arr) return get_10_(arr, 1) end
local get_any = function(arr)
local tmp = {}
copy_table(tmp, arr[1])
local a01 = get_01(arr)
copy_table(tmp, a01)
return tmp
end
local format_el = function(el, comment)
if el['name'] then
return { name = el['name'] .. comment, el[1] }
else
return el .. comment
for k, v in pairs(t2c) do
if not t1c[k] then
t2only_c[k] = v
end
end
end
local format_block_12 = function(t, name)
local t11 = get_11(t)
local t10 = get_10(t)
local t01 = get_01(t)
local r = { name = name }
if #t11 == 0 and #t10 == 0 and #t01 == 0 then return {} end
for _, el in ipairs(t11) do table.insert(r, el) end
for _, el in ipairs(t10) do table.insert(r, format_el(el, ' (before TLS stream)')) end
for _, el in ipairs(t01) do table.insert(r, format_el(el, ' (in TLS stream)')) end
return r
end
local format_block_1 = function(t, name)
local res = { name = name }
if #t[1] == 0 then return {} end
copy_table(res, t[1])
return res
both[cat] = (both_c() and both_c) or nil
t1only[cat] = (t1only_c() and t1only_c) or nil
t2only[cat] = (t2only_c() and t2only_c) or nil
end
--ordered list categories
for _, cat in ipairs({"features", "compression_methods", "auth_mechanisms", "errors", "unknown"}) do
local t1only_c = {}
local t2only_c = {}
local both_c = {}
local t1c = t1[cat]
local t2c = t2[cat]
local union = {}
for _, v in ipairs(t1c) do
union[v] = 1
end
for _, v in ipairs(t2c) do
if union[v] then
union[v] = 2
else
table.insert(t2only_c, v)
end
end
for v, num in pairs(union) do
if num == 1 then
table.insert(t1only_c, v)
else
table.insert(both_c, v)
end
end
both[cat] = (next(both_c) and both_c) or nil
t1only[cat] = (next(t1only_c) and t1only_c) or nil
t2only[cat] = (next(t2only_c) and t2only_c) or nil
end
return both, t1only, t2only
end
portrule = shortport.port_or_service({5222, 5269}, {"jabber", "xmpp-client", "xmpp-server"})
action = function(host, port)
local server_name = stdnse.get_script_args("xmpp-info.server_name") or host.targetname or host.name
local alt_server_name = stdnse.get_script_args("xmpp-info.alt_server_name") or "."
local err_tmp = { {}, {} }
local id_tls
local tls_result
local starttls_failed
stdnse.print_debug(2, "%s: %s", SCRIPT_NAME, "server = " .. server_name)
local id2 = scan(host, port, alt_server_name, false, 1)
copy_table(err_tmp[1], err[1])
local altname_result = scan(host, port, alt_server_name, false)
local id1 = scan(host, port, server_name, false, 1)
copy_table(err_tmp[2], err[1])
local plain_result = scan(host, port, server_name, false)
server_info(host, port, id1, id2)
server_info(host, port, altname_result["stream_id"], plain_result["stream_id"])
if not stdnse.get_script_args("xmpp-info.no_starttls") then
id_tls = scan(host, port, server_name, true, 2)
if not id_tls then starttls_failed = 1 end
tls_result = scan(host, port, server_name, true)
if not tls_result then starttls_failed = 1 end
end
local r = {}
local r = stdnse.output_table()
local format_block = format_block_12
if not id_tls then
format_block = format_block_1
if #altname_result["errors"] == 0 and #plain_result["errors"] == 0 then
table.insert(r, "Ignores server name")
elseif #altname_result["errors"] ~= #plain_result["errors"] then
table.insert(r, "Respects server name")
end
if not tls_result then
if starttls_failed then table.insert(r, "STARTTLS Failed") end
r["info"] = plain_result
else
local i,p,t = factor(plain_result, tls_result)
r["info"] = (i() and i) or nil
r["pre_tls"] = (p() and p) or nil
r["post_tls"] = (t() and t) or nil
end
table.insert(r, format_block(t_xmpp, "XMPP"))
table.insert(r, format_block(features_list, "features"))
table.insert(r, format_block(caps, "capabilities"))
table.insert(r, format_block(methods, "COMPRESSION METHODS (" .. #(get_any(methods)) .. ")"))
table.insert(r, format_block(mechanisms, "AUTH MECHANISMS (" .. #get_any(mechanisms) .. ")"))
table.insert(r, format_block(err, "errors"))
local l = { name = 'Unknown features (please report about it on nmap-dev@)' }
copy_table(l, unknown)
table.insert(r, l)
if #err_tmp[1] == 0 or #err_tmp[2] == 0 then
if (#err_tmp[1] > 0) ~= (#err_tmp[2] > 0) then
table.insert(r, "Respects server name")
else
table.insert(r, "Ignores server name")
end
end
return stdnse.format_output(true, r)
return r
end