Sat, 18 Sep 2010 18:19:37 +0100
Initial commit
0 | 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 = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; | |
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 = (" <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>"); |