|
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 |