capscan.lua

changeset 0
d17a1b659852
child 1
ce892ac8bec2
equal deleted inserted replaced
-1:000000000000 0:d17a1b659852
1 require "verse"
2 require "verse.client"
3 local array = require "array";
4 local calculate_hash = require "caps".calculate_hash;
5
6 -- Configurable:
7 local jid, pass = arg[1], nil;
8 --- -- -- -- ---
9
10 local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
11 local function escape(str) return (string.gsub(str, "['&<>\"]", escape_table)); end
12
13 local xmlns_caps = "http://jabber.org/protocol/caps";
14 local xmlns_disco = "http://jabber.org/protocol/disco#info";
15
16 if not pass then
17 io.write("Password (not blanked): ");
18 pass = io.read("*l");
19 end
20
21 local conn = verse.new(verse.logger());
22
23 conn:add_plugin("version");
24
25 conn:connect_client(jid, pass);
26
27 local contacts = {};
28
29 conn:hook("ready", function ()
30 conn:hook("presence", function (presence)
31 if contacts[presence.attr.from] then return; end;
32
33 local contact = { jid = presence.attr.from };
34 contacts[contact.jid] = contact;
35
36 local caps_tag = presence:get_child("c", xmlns_caps);
37 if not caps_tag then
38 conn:debug("No caps from %s: %s", presence.attr.from, tostring(presence));
39 return;
40 end
41
42 contact.hash_type = caps_tag.attr.hash;
43 contact.ver = caps_tag.attr.ver;
44 contact.node = caps_tag.attr.node;
45
46 local node_string = contact.node.."#"..contact.ver;
47
48 conn:query_version(contact.jid, function (version)
49 contact.version = ("%s%s%s"):format(
50 version.name or "[no name]",
51 version.version and (" "..version.version) or "",
52 version.platform and (" ("..version.platform..")") or ""
53 );
54 end);
55
56 conn:send_iq(verse.iq({ to = contact.jid, type = "get" })
57 :tag("query", { xmlns = xmlns_disco, node = node_string }),
58 function (result)
59 if result.attr.type == "error" then
60 contact.error = { result:get_error() };
61 return;
62 end
63 contact.calculated_ver, contact.calculated_S
64 = calculate_hash(result.tags[1]);
65 if contact.calculated_ver ~= contact.ver then
66 conn:warn("Invalid caps hash: %s", contact.jid);
67 conn:warn("Received: %s Calculated: %s", contact.ver, contact.calculated_ver);
68 conn:warn("Received stanza: %s", tostring(result));
69 conn:warn("Calculated S: %s", contact.calculated_S);
70 else
71 conn:warn("Valid caps hash: %s", contact.jid);
72 end
73 end);
74 end, 1000);
75 conn:send(verse.presence():tag("priority"):text("-1"));
76 end);
77
78 verse.loop();
79
80 --- Write report
81
82 local report = io.open("report.html", "w+");
83 report:write[[<html>
84 <head>
85 <title>]];
86 report:write("Entity capabilities validity for contacts of ", jid);
87 report:write[[</head>
88 <body>
89 ]];
90
91 report:write[[<style type="text/css">
92 .good-hash { background-color: #33aa33; }
93 .bad-hash { background-color: #aa3333; }
94 .no-hash { background-color: #ffffff; }
95 .unknown-hash { background-color: #aaaa33; }
96 </style>
97 ]]
98
99
100 local contact_jids = array.collect(keys(contacts)):sort();
101
102 report:write("<h1>Entity capabilities report for contacts of ", jid, "</h1>\n");
103 report:write("<p>", tostring(#contact_jids), " contacts</p>\n");
104
105 local function write_section(title, jids, show)
106 report:write("\n<h1>", title, "</h1>\n");
107 report:write("<p>", tostring(#jids), " contacts</p>\n");
108 report:write("<ul>\n");
109 for _, jid in ipairs(jids) do
110 local contact = contacts[jid];
111 local client_link = ("&nbsp;<a href='%s'>%s</a>"):format(
112 escape(contacts[jid].node or "#"),
113 escape(contacts[jid].version or "Unknown client")
114 );
115 report:write(" <li>", escape(jid), client_link);
116
117 if show then
118 report:write("\n <ul>\n");
119 for _, field in ipairs(show) do
120 local friendly_field = field:gsub("^.", string.upper):gsub("_", " ");
121 local value = escape(contacts[jid][field] or "");
122 report:write((" "):rep(12), "<li>", friendly_field, ": ", value, "</li>\n");
123 end
124 report:write(" </ul>\n");
125 end
126
127 report:write("</li>\n");
128 end
129 report:write("</ul>\n");
130 end
131
132 local function no_caps_filter(jid)
133 return not contacts[jid].ver;
134 end
135
136 local function legacy_caps_filter(jid)
137 return contacts[jid].ver and not contacts[jid].hash_type;
138 end
139
140 local function valid_caps_filter(jid)
141 return not(legacy_caps_filter(jid))
142 and (contacts[jid].ver == contacts[jid].calculated_ver);
143 end
144
145 local function invalid_caps_filter(jid)
146 return not(legacy_caps_filter(jid)) and not(no_caps_filter(jid)) and not(valid_caps_filter(jid));
147 end
148
149 write_section("Valid caps", array.filter(contact_jids, valid_caps_filter));
150 write_section("Invalid caps", array.filter(contact_jids, invalid_caps_filter), { "ver", "calculated_ver", "calculated_S" });
151 write_section("No caps", array.filter(contact_jids, no_caps_filter));
152 write_section("Legacy caps", array.filter(contact_jids, legacy_caps_filter), { "ver" });
153
154 report:write("</body></html>");

mercurial