plugins/rtbl_guard.lua

changeset 173
e46ac57fa60b
equal deleted inserted replaced
172:3420808b8d3f 173:e46ac57fa60b
1 local jid_bare = require "util.jid".bare;
2 local jid_prep = require "util.jid".prep;
3 local sha256 = require "util.hashes".sha256;
4
5 local function get_ban_reason(blocklist, entry)
6 local msg = { ("Banned by %s"):format(blocklist.name or "RTBL"), nil, nil };
7
8 local reason = entry.reason:match("^urn:xmpp:reporting:(%w+)$");
9 if reason then
10 msg[2] = "for "..reason;
11 end
12
13 if entry.text then
14 msg[#msg+1] = "("..entry.text..")";
15 end
16
17 return table.concat(msg, " ");
18 end
19
20 function riddim.plugins.rtbl_guard(bot)
21 bot.stream:add_plugin("pubsub");
22
23 local config = bot.config.rtbl_guard;
24 if not config then return; end
25
26 -- {
27 -- [rtbl_name] = {
28 -- entries = {
29 -- [jid/hash] = {
30 -- reason = <string>
31 -- text = <string>
32 -- }
33 -- }
34 -- }
35 local blocklists = {};
36
37 local function check_blocklists(jid, only_blocklist, only_entry_id)
38 jid = jid_bare(jid_prep(jid));
39 local hash = sha256(jid, true);
40
41 if only_blocklist and only_entry_id then
42 if only_entry_id ~= jid and only_entry_id ~= hash then
43 bot.stream:debug("No match for single entry %q == (%q | %q)", only_entry_id, jid, hash)
44 return;
45 end
46 return only_blocklist.entries[only_entry_id], only_blocklist;
47 end
48
49 for rtbl_id, blocklist in pairs(only_blocklist and {only_blocklist} or blocklists) do
50 bot.stream:debug("Checking %s for (%q | %q)", rtbl_id, jid, hash);
51 for k in pairs(blocklist.entries) do
52 bot.stream:debug("[%q]", k);
53 end
54 local entry = blocklist.entries[hash] or blocklist.entries[jid];
55 if entry then
56 return entry, blocklist;
57 end
58 end
59 end
60
61 local function guard_room(room)
62
63 local function process_occupant(occupant, only_blocklist, only_entry_id)
64 if not occupant.real_jid then
65 bot.stream:debug("Unable to determine real JID for %s - skipping RTBL checks", occupant.nick);
66 return;
67 end
68
69 local matched_entry, matched_blocklist = check_blocklists(occupant.real_jid, only_blocklist, only_entry_id);
70 if not matched_entry then
71 bot.stream:debug("%s is not on any RTBLs", occupant.nick);
72 return;
73 end
74
75 room:ban(occupant.nick, get_ban_reason(matched_blocklist, matched_entry));
76 end
77
78 -- Check future occupants when they join
79 room:hook("occupant-joined", process_occupant);
80
81 local function process_all_occupants(blocklist, entry_id)
82 bot.stream:debug("Checking all occupants of %s against RTBL (%s)", room.jid, entry_id or "all entries");
83 for _, occupant in pairs(room.occupants) do
84 process_occupant(occupant, blocklist, entry_id);
85 end
86 end
87
88 -- Check existing occupants against all existing entries
89 process_all_occupants();
90
91 -- Check existing occupants when new entries are added
92 room:hook("rtbl-entry-added", function (event)
93 process_all_occupants(event.blocklist, event.entry_id);
94 end);
95 room:hook("rtbl-updated", function (event)
96 process_all_occupants(event.blocklist);
97 end);
98 end
99
100 local function handle_entry(blocklist, item)
101 local report = item:get_child("report", "urn:xmpp:reporting:1");
102 if not report then return; end
103 blocklist.entries[item.attr.id] = {
104 reason = report.attr.reason;
105 text = report:get_child_text("text");
106 };
107 return blocklist.entries[item.attr.id];
108 end
109
110 local function clear_entry(blocklist, retract)
111 local cleared_entry = blocklist.entries[retract.attr.id];
112 if not cleared_entry then
113 return;
114 end
115 blocklist.entries[retract.attr.id] = nil;
116 return cleared_entry;
117 end
118
119 bot.stream:hook("pubsub/event", function (event)
120 local blocklist_id = event.from.."::"..event.node;
121 local blocklist = blocklists[blocklist_id];
122 if not blocklist then return; end
123 local entry = handle_entry(blocklist, event.item);
124 for _, room in pairs(bot.rooms) do
125 room:event("rtbl-entry-added", {
126 blocklist = blocklist;
127 entry = entry;
128 entry_id = event.item.attr.id;
129 });
130 end
131 end);
132
133 -- COMPAT: current verse does not emit an event for retraction
134 bot.stream:hook("message", function (message)
135 local m_from = message.attr.from;
136 for pubsub_event in message:childtags("event", "http://jabber.org/protocol/pubsub#event") do
137 local items = pubsub_event:get_child("items");
138 if items then
139 local node = items.attr.node;
140 local blocklist_id = m_from.."::"..node;
141 local blocklist = blocklists[blocklist_id];
142 if not blocklist then return; end
143
144 for retract in items:childtags("retract") do
145 local entry = clear_entry(blocklist, retract);
146 if entry then
147 for _, room in pairs(bot.rooms) do
148 room:event("rtbl-entry-removed", {
149 blocklist = blocklist;
150 entry = entry;
151 });
152 end
153 end
154 end
155 end
156 end
157 end);
158
159 bot:hook("started", function ()
160 for _, rtbl_config in ipairs(config) do
161 local host, node = rtbl_config.host, rtbl_config.node;
162 if host and node then
163 bot.stream.pubsub(host, node):subscribe(nil, nil, function (response)
164 if response.attr.type ~= "result" then
165 bot.stream:warn("Failed to subscribe to RTBL %s::%s", host, node);
166 return;
167 end
168 bot.stream:info("Subscribed to RTBL %s::%s", host, node);
169
170 local blocklist = { name = rtbl_config.name, entries = {} };
171 bot.stream.pubsub(host, node):items(true, function (items_response)
172 if items_response.attr.type ~= "result" then
173 bot.stream:warn("Failed to synchronize with RTBL: %s", tostring(items_response));
174 return;
175 end
176 local items = items_response.tags[1].tags[1];
177 bot.stream:debug("RTBL sync: %s", items);
178 for item in items:childtags("item") do
179 bot.stream:debug("RTBL item: %s", item);
180 handle_entry(blocklist, item);
181 end
182
183 for _, room in pairs(bot.rooms) do
184 room:event("rtbl-updated", {
185 blocklist = blocklist;
186 });
187 end
188 end);
189 local blocklist_id = host.."::"..node;
190 blocklists[blocklist_id] = blocklist;
191 end);
192 end
193 end
194
195 for _, room in pairs(bot.rooms) do
196 guard_room(room);
197 end
198
199 bot:hook("groupchat/joined", guard_room);
200 end);
201 end

mercurial