capscan.lua

changeset 0
d17a1b659852
child 1
ce892ac8bec2
--- /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 = { ["'"] = "&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 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[[<html>
+<head>
+	<title>]];
+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 = ("&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