Skip to content

Commit 9458c5b

Browse files
committed
GanneffServ: make it possible to not kill irccloud
Without this, anyone using irccloud (or a similar service) can get all other users k:lined by tripping on a trap (J). With this, they should be treated like a tor user (getting themselves killed).
1 parent 9802676 commit 9458c5b

File tree

3 files changed

+136
-2
lines changed

3 files changed

+136
-2
lines changed

languages/ganneffserv.en.lang

+15
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,18 @@ GS_HLP_SRV_LONG
7575
IN ROTATION.
7676

7777
EVERY user that connects on this server WILL BE KLINED!
78+
GS_HLP_PRT_SHORT
79+
%s: Protect users from akill triggered by another similar user
80+
GS_HLP_PRT_LONG
81+
Takes a hostmask (or a regular expression if it starts with ^).
82+
83+
When a matching user trips on a trap, only they will be killed
84+
instead of causing collateral damage to other matching users.
85+
86+
Usage: PROTECT <mask|regex> :<reason>
87+
GS_HLP_UPR_SHORT
88+
%s: Delete protection for adjacent users
89+
GS_HLP_UPR_LONG
90+
Delete collateral damage protection for matching users.
91+
92+
Usage: UNPROTECT <mask|regex>

modules/GanneffServ.rb

+111-2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ def initialize()
6969
["STATS", 0, 0, SFLG_NOMAXPARAM, ADMIN_FLAG, lm('GS_HLP_STS_SHORT'), lm('GS_HLP_STS_LONG')],
7070
["ENFORCE", 0, 0, SFLG_NOMAXPARAM, ADMIN_FLAG, lm('GS_HLP_ENF_SHORT'), lm('GS_HLP_ENF_LONG')],
7171
["BADSERV", 0, 1, SFLG_NOMAXPARAM, ADMIN_FLAG, lm('GS_HLP_SRV_SHORT'), lm('GS_HLP_SRV_LONG')],
72+
["PROTECT", 1, 2, SFLG_NOMAXPARAM, ADMIN_FLAG, lm('GS_HLP_PRT_SHORT'), lm('GS_HLP_PRT_LONG')],
73+
["UNPROTECT",1, 2, SFLG_NOMAXPARAM, ADMIN_FLAG, lm('GS_HLP_UPR_SHORT'), lm('GS_HLP_UPR_LONG')],
7274
]) # register
7375

7476
# Which hooks do we want?
@@ -100,6 +102,13 @@ def initialize()
100102
irc_lower($1)')
101103
@dbq['INCREASE_KILLS'] = DB.prepare('UPDATE ganneffserv SET kills = kills+1
102104
WHERE irc_lower(channel) = irc_lower($1)')
105+
@dbq['INSERT_PROTECT'] = DB.prepare('INSERT INTO ganneffprotect(setter, time,
106+
pattern, reason) VALUES($1, $2, $3, $4)')
107+
@dbq['DELETE_PROTECT'] = DB.prepare('DELETE FROM ganneffprotect WHERE
108+
irc_lower(pattern) = irc_lower($1)')
109+
@dbq['GET_PROTECTED_PATTERNS'] = DB.prepare('SELECT pattern, reason FROM ganneffprotect')
110+
@dbq['GET_PROTECTED_PATTERNS_DETAILED'] = DB.prepare('SELECT pattern, setter, time,
111+
reason FROM ganneffprotect')
103112
end # def initialize
104113

105114
########################################################################
@@ -221,6 +230,50 @@ def DEL(client, parv = [])
221230
true
222231
end # def DEL
223232

233+
# ------------------------------------------------------------------------
234+
235+
# Protect users from collatoral damage
236+
def PROTECT(client, parv = [])
237+
parv[1].downcase!
238+
debug(LOG_DEBUG, "#{client.name} called PROTECT and the params are #{parv.join(",")}")
239+
240+
requested_pattern = parv[1]
241+
pattern = irc_pattern_to_regex(requested_pattern)
242+
reason = parv[2]
243+
244+
ret = DB.execute_nonquery(@dbq['INSERT_PROTECT'], 'iiss', client.nick.account_id,
245+
Time.now.to_i, pattern, reason)
246+
if ret then
247+
debug(LOG_NOTICE, "#{client.name} added protection #{pattern}, reason #{reason}")
248+
@protection[pattern] = reason
249+
load_protected_patterns
250+
reply(client, "Protection #{requested_pattern} successfully added")
251+
else
252+
reply(client, "Failed to add #{requested_pattern}")
253+
end
254+
end
255+
256+
# ------------------------------------------------------------------------
257+
258+
# Unprotect users from collatoral damage
259+
def UNPROTECT(client, parv = [])
260+
parv[1].downcase!
261+
debug(LOG_DEBUG, "#{client.name} called UNPROTECT and the params are #{parv.join(",")}")
262+
263+
pattern = irc_pattern_to_regex(parv[1])
264+
return unless @protection.has_key?(pattern)
265+
266+
ret = DB.execute_nonquery(@dbq['DELETE_PROTECT'], 's', pattern)
267+
if ret then
268+
debug(LOG_NOTICE, "#{client.name} removed protection #{pattern}")
269+
@protection.delete(pattern)
270+
load_protected_patterns
271+
reply(client, "Protection #{pattern} successfully deleted.")
272+
else
273+
reply(client, "Failed to delete protection #{pattern}.")
274+
end
275+
end
276+
224277
# ------------------------------------------------------------------------
225278

226279
# List all channels we monitor
@@ -245,6 +298,18 @@ def LIST(client, parv = [])
245298
}
246299
result.free
247300

301+
reply(client, "Protected host patterns\n\n")
302+
reply(client, "%-50s %-10s %-19s %s" % [ "Pattern", "By", "When", "Reason" ])
303+
result = DB.execute(@dbq['GET_PROTECTED_PATTERNS_DETAILED'])
304+
result.row_each { |row|
305+
pattern = row[0]
306+
by = row[1]
307+
time = Time.at(row[2].to_i).strftime('%Y-%m-%d %H:%M:%S')
308+
reason = row[3]
309+
reply(client, "%-50s %-10s %-19s %s" % [ pattern, by, time, reason ])
310+
}
311+
result.free
312+
248313
reply(client, "\nCRFJ - checks Connect, Register nick, Join channel within 15 seconds (i.e. Fast)")
249314
reply(client, "J - triggers on every Join")
250315

@@ -574,8 +639,13 @@ def akill(client, reason, operreason, channel="")
574639
ret = kill_user(client, reason)
575640
else # if host
576641
reason = "#{reason}|#{operreason}"
577-
debug(LOG_DEBUG, "Issuing AKILL: *@#{host}, #{reason} lasting for #{@akill_duration} seconds")
578-
ret = akill_add("*@#{host}", reason, @akill_duration)
642+
if client.host =~ /#{@protected_patterns}/i # if protected hosts
643+
debug(LOG_DEBUG, "Using /kill instead of AKILL for protected user #{client.name}")
644+
ret = kill_user(client, reason)
645+
else
646+
debug(LOG_DEBUG, "Issuing AKILL: *@#{host}, #{reason} lasting for #{@akill_duration} seconds")
647+
ret = akill_add("*@#{host}", reason, @akill_duration)
648+
end # if protected hosts
579649
end # if host
580650

581651
channel.downcase!
@@ -596,6 +666,31 @@ def akill(client, reason, operreason, channel="")
596666
end # if kill_user
597667
end # def akill
598668

669+
# ------------------------------------------------------------------------
670+
671+
# convert irc pattern to regular expression
672+
def irc_pattern_to_regex(pattern
673+
# "." -> "\.", "*" -> ".*", "?" -> "."
674+
# wrap with "^...$"
675+
return pattern if pattern.start_with? '^'
676+
pattern = pattern.gsub(/\./, '\\.')
677+
.gsub(/\*/, '.*')
678+
.gsub(/\?/, '.')
679+
return "^#{pattern}$"
680+
end
681+
682+
# ------------------------------------------------------------------------
683+
684+
# get protected patterns as pattern
685+
def load_protected_patterns()
686+
patterns = @protection.keys
687+
if patterns.empty?
688+
@protected_patterns = '^$'
689+
else
690+
@protected_patterns = patterns.join('|')
691+
end
692+
end # def get_protected_patterns
693+
599694
# ------------------------------------------------------------------------
600695

601696
# enforce a channel - kill all of its users
@@ -648,6 +743,20 @@ def load_data()
648743
count += 1
649744
}
650745
result.free
746+
747+
@protection = Hash.new
748+
result = DB.execute(@dbq['GET_PROTECTED_PATTERNS'], '')
749+
count = 0
750+
result.row_each { |row|
751+
pattern = row[0]
752+
reason = row[1]
753+
pattern = irc_pattern_to_regex(pattern)
754+
@protection[pattern] = reason
755+
count += 1
756+
}
757+
result.free
758+
load_protected_patterns
759+
651760
debug(LOG_DEBUG, "All channel data successfully loaded")
652761
end # def load_data
653762

sql/ganneffserv-pgsql.sql

+10
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,14 @@ CREATE TABLE ganneffserv (
88
kills INTEGER NOT NULL DEFAULT 0,
99
monitor_only BOOLEAN NOT NULL DEFAULT 'False'
1010
);
11+
12+
DROP TABLE IF EXISTS ganneffprotect CASCADE;
13+
CREATE TABLE ganneffprotect (
14+
id SERIAL PRIMARY KEY,
15+
setter INTEGER REFERENCES account(id) ON DELETE SET NULL,
16+
time INTEGER NOT NULL,
17+
pattern VARCHAR(255) NOT NULL,
18+
reason VARCHAR(255) NOT NULL
19+
)
20+
1121
CREATE UNIQUE INDEX ganneffserv_channel_idx ON ganneffserv (irc_lower(channel));

0 commit comments

Comments
 (0)