diff -r 000000000000 -r d17a1b659852 capscan.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/capscan.lua Sat Sep 18 18:19:37 2010 +0100 @@ -0,0 +1,154 @@ +require "verse" +require "verse.client" +local array = require "array"; +local calculate_hash = require "caps".calculate_hash; + +-- Configurable: +local jid, pass = arg[1], nil; +--- -- -- -- --- + +local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; +local function escape(str) return (string.gsub(str, "['&<>\"]", escape_table)); end + +local xmlns_caps = "http://jabber.org/protocol/caps"; +local xmlns_disco = "http://jabber.org/protocol/disco#info"; + +if not pass then + io.write("Password (not blanked): "); + pass = io.read("*l"); +end + +local conn = verse.new(verse.logger()); + +conn:add_plugin("version"); + +conn:connect_client(jid, pass); + +local contacts = {}; + +conn:hook("ready", function () + conn:hook("presence", function (presence) + if contacts[presence.attr.from] then return; end; + + local contact = { jid = presence.attr.from }; + contacts[contact.jid] = contact; + + local caps_tag = presence:get_child("c", xmlns_caps); + if not caps_tag then + conn:debug("No caps from %s: %s", presence.attr.from, tostring(presence)); + return; + end + + contact.hash_type = caps_tag.attr.hash; + contact.ver = caps_tag.attr.ver; + contact.node = caps_tag.attr.node; + + local node_string = contact.node.."#"..contact.ver; + + conn:query_version(contact.jid, function (version) + contact.version = ("%s%s%s"):format( + version.name or "[no name]", + version.version and (" "..version.version) or "", + version.platform and (" ("..version.platform..")") or "" + ); + end); + + conn:send_iq(verse.iq({ to = contact.jid, type = "get" }) + :tag("query", { xmlns = xmlns_disco, node = node_string }), + function (result) + if result.attr.type == "error" then + contact.error = { result:get_error() }; + return; + end + contact.calculated_ver, contact.calculated_S + = calculate_hash(result.tags[1]); + if contact.calculated_ver ~= contact.ver then + conn:warn("Invalid caps hash: %s", contact.jid); + conn:warn("Received: %s Calculated: %s", contact.ver, contact.calculated_ver); + conn:warn("Received stanza: %s", tostring(result)); + conn:warn("Calculated S: %s", contact.calculated_S); + else + conn:warn("Valid caps hash: %s", contact.jid); + end + end); + end, 1000); + conn:send(verse.presence():tag("priority"):text("-1")); +end); + +verse.loop(); + +--- Write report + +local report = io.open("report.html", "w+"); +report:write[[ + + ]]; +report:write("Entity capabilities validity for contacts of ", jid); +report:write[[</head> +<body> +]]; + +report:write[[<style type="text/css"> + .good-hash { background-color: #33aa33; } + .bad-hash { background-color: #aa3333; } + .no-hash { background-color: #ffffff; } + .unknown-hash { background-color: #aaaa33; } +</style> +]] + + +local contact_jids = array.collect(keys(contacts)):sort(); + +report:write("<h1>Entity capabilities report for contacts of ", jid, "</h1>\n"); +report:write("<p>", tostring(#contact_jids), " contacts</p>\n"); + +local function write_section(title, jids, show) + report:write("\n<h1>", title, "</h1>\n"); + report:write("<p>", tostring(#jids), " contacts</p>\n"); + report:write("<ul>\n"); + for _, jid in ipairs(jids) do + local contact = contacts[jid]; + local client_link = (" <a href='%s'>%s</a>"):format( + escape(contacts[jid].node or "#"), + escape(contacts[jid].version or "Unknown client") + ); + report:write(" <li>", escape(jid), client_link); + + if show then + report:write("\n <ul>\n"); + for _, field in ipairs(show) do + local friendly_field = field:gsub("^.", string.upper):gsub("_", " "); + local value = escape(contacts[jid][field] or ""); + report:write((" "):rep(12), "<li>", friendly_field, ": ", value, "</li>\n"); + end + report:write(" </ul>\n"); + end + + report:write("</li>\n"); + end + report:write("</ul>\n"); +end + +local function no_caps_filter(jid) + return not contacts[jid].ver; +end + +local function legacy_caps_filter(jid) + return contacts[jid].ver and not contacts[jid].hash_type; +end + +local function valid_caps_filter(jid) + return not(legacy_caps_filter(jid)) + and (contacts[jid].ver == contacts[jid].calculated_ver); +end + +local function invalid_caps_filter(jid) + return not(legacy_caps_filter(jid)) and not(no_caps_filter(jid)) and not(valid_caps_filter(jid)); +end + +write_section("Valid caps", array.filter(contact_jids, valid_caps_filter)); +write_section("Invalid caps", array.filter(contact_jids, invalid_caps_filter), { "ver", "calculated_ver", "calculated_S" }); +write_section("No caps", array.filter(contact_jids, no_caps_filter)); +write_section("Legacy caps", array.filter(contact_jids, legacy_caps_filter), { "ver" }); + +report:write("</body></html>");