capscan.lua

Tue, 21 Sep 2010 10:22:30 +0100

author
will@willthompson.co.uk
date
Tue, 21 Sep 2010 10:22:30 +0100
changeset 5
db500386b9c4
parent 4
9a4a18d11166
permissions
-rw-r--r--

Correctly close <title> tag in report.html

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 = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
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 jid then
	io.write("Please give a JID as the first argument\n");
	os.exit(1);
end

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] or presence.attr.type 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[[<html>
<head>
	<title>]];
report:write("Entity capabilities validity for contacts of ", jid);
report:write[[</title>
</head>
<body>
]];

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 = ("&nbsp;<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>");

mercurial