From 9802676d84d3c37daa5734f045f9235d9dd368cf Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sun, 14 Nov 2021 15:54:45 -0500 Subject: [PATCH 1/3] GanneffServ: Escape regexp field --- modules/GanneffServ.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/GanneffServ.rb b/modules/GanneffServ.rb index f9abcd6..ab1286c 100644 --- a/modules/GanneffServ.rb +++ b/modules/GanneffServ.rb @@ -326,7 +326,7 @@ def BADSERV(client, parv = []) debug(LOG_DEBUG, "#{client.name} called BADSERV and the parms are #{parv.join(",")}") server = parv[1].downcase - if server =~ /.*\.oftc.net$/ + if server =~ /.*\.oftc\.net$/ debug(LOG_DEBUG, "#{server} seems to be an oftc server, proceeding") @badserver = server reply(client, "#{server} is now marked as a bad server, all new connections will be killed") From 9458c5b26a71619a4f3d77cb3037c6c85a7fffca Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Sun, 14 Nov 2021 16:25:02 -0500 Subject: [PATCH 2/3] 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). --- languages/ganneffserv.en.lang | 15 +++++ modules/GanneffServ.rb | 113 +++++++++++++++++++++++++++++++++- sql/ganneffserv-pgsql.sql | 10 +++ 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/languages/ganneffserv.en.lang b/languages/ganneffserv.en.lang index 3f18c4b..6b4c26a 100644 --- a/languages/ganneffserv.en.lang +++ b/languages/ganneffserv.en.lang @@ -75,3 +75,18 @@ GS_HLP_SRV_LONG IN ROTATION. EVERY user that connects on this server WILL BE KLINED! +GS_HLP_PRT_SHORT + %s: Protect users from akill triggered by another similar user +GS_HLP_PRT_LONG + Takes a hostmask (or a regular expression if it starts with ^). + + When a matching user trips on a trap, only they will be killed + instead of causing collateral damage to other matching users. + + Usage: PROTECT : +GS_HLP_UPR_SHORT + %s: Delete protection for adjacent users +GS_HLP_UPR_LONG + Delete collateral damage protection for matching users. + + Usage: UNPROTECT  diff --git a/modules/GanneffServ.rb b/modules/GanneffServ.rb index ab1286c..dfc2bb7 100644 --- a/modules/GanneffServ.rb +++ b/modules/GanneffServ.rb @@ -69,6 +69,8 @@ def initialize() ["STATS", 0, 0, SFLG_NOMAXPARAM, ADMIN_FLAG, lm('GS_HLP_STS_SHORT'), lm('GS_HLP_STS_LONG')], ["ENFORCE", 0, 0, SFLG_NOMAXPARAM, ADMIN_FLAG, lm('GS_HLP_ENF_SHORT'), lm('GS_HLP_ENF_LONG')], ["BADSERV", 0, 1, SFLG_NOMAXPARAM, ADMIN_FLAG, lm('GS_HLP_SRV_SHORT'), lm('GS_HLP_SRV_LONG')], + ["PROTECT", 1, 2, SFLG_NOMAXPARAM, ADMIN_FLAG, lm('GS_HLP_PRT_SHORT'), lm('GS_HLP_PRT_LONG')], + ["UNPROTECT",1, 2, SFLG_NOMAXPARAM, ADMIN_FLAG, lm('GS_HLP_UPR_SHORT'), lm('GS_HLP_UPR_LONG')], ]) # register # Which hooks do we want? @@ -100,6 +102,13 @@ def initialize() irc_lower($1)') @dbq['INCREASE_KILLS'] = DB.prepare('UPDATE ganneffserv SET kills = kills+1 WHERE irc_lower(channel) = irc_lower($1)') + @dbq['INSERT_PROTECT'] = DB.prepare('INSERT INTO ganneffprotect(setter, time, + pattern, reason) VALUES($1, $2, $3, $4)') + @dbq['DELETE_PROTECT'] = DB.prepare('DELETE FROM ganneffprotect WHERE + irc_lower(pattern) = irc_lower($1)') + @dbq['GET_PROTECTED_PATTERNS'] = DB.prepare('SELECT pattern, reason FROM ganneffprotect') + @dbq['GET_PROTECTED_PATTERNS_DETAILED'] = DB.prepare('SELECT pattern, setter, time, + reason FROM ganneffprotect') end # def initialize ######################################################################## @@ -221,6 +230,50 @@ def DEL(client, parv = []) true end # def DEL +# ------------------------------------------------------------------------ + + # Protect users from collatoral damage + def PROTECT(client, parv = []) + parv[1].downcase! + debug(LOG_DEBUG, "#{client.name} called PROTECT and the params are #{parv.join(",")}") + + requested_pattern = parv[1] + pattern = irc_pattern_to_regex(requested_pattern) + reason = parv[2] + + ret = DB.execute_nonquery(@dbq['INSERT_PROTECT'], 'iiss', client.nick.account_id, + Time.now.to_i, pattern, reason) + if ret then + debug(LOG_NOTICE, "#{client.name} added protection #{pattern}, reason #{reason}") + @protection[pattern] = reason + load_protected_patterns + reply(client, "Protection #{requested_pattern} successfully added") + else + reply(client, "Failed to add #{requested_pattern}") + end + end + +# ------------------------------------------------------------------------ + + # Unprotect users from collatoral damage + def UNPROTECT(client, parv = []) + parv[1].downcase! + debug(LOG_DEBUG, "#{client.name} called UNPROTECT and the params are #{parv.join(",")}") + + pattern = irc_pattern_to_regex(parv[1]) + return unless @protection.has_key?(pattern) + + ret = DB.execute_nonquery(@dbq['DELETE_PROTECT'], 's', pattern) + if ret then + debug(LOG_NOTICE, "#{client.name} removed protection #{pattern}") + @protection.delete(pattern) + load_protected_patterns + reply(client, "Protection #{pattern} successfully deleted.") + else + reply(client, "Failed to delete protection #{pattern}.") + end + end + # ------------------------------------------------------------------------ # List all channels we monitor @@ -245,6 +298,18 @@ def LIST(client, parv = []) } result.free + reply(client, "Protected host patterns\n\n") + reply(client, "%-50s %-10s %-19s %s" % [ "Pattern", "By", "When", "Reason" ]) + result = DB.execute(@dbq['GET_PROTECTED_PATTERNS_DETAILED']) + result.row_each { |row| + pattern = row[0] + by = row[1] + time = Time.at(row[2].to_i).strftime('%Y-%m-%d %H:%M:%S') + reason = row[3] + reply(client, "%-50s %-10s %-19s %s" % [ pattern, by, time, reason ]) + } + result.free + reply(client, "\nCRFJ - checks Connect, Register nick, Join channel within 15 seconds (i.e. Fast)") reply(client, "J - triggers on every Join") @@ -574,8 +639,13 @@ def akill(client, reason, operreason, channel="") ret = kill_user(client, reason) else # if host reason = "#{reason}|#{operreason}" - debug(LOG_DEBUG, "Issuing AKILL: *@#{host}, #{reason} lasting for #{@akill_duration} seconds") - ret = akill_add("*@#{host}", reason, @akill_duration) + if client.host =~ /#{@protected_patterns}/i # if protected hosts + debug(LOG_DEBUG, "Using /kill instead of AKILL for protected user #{client.name}") + ret = kill_user(client, reason) + else + debug(LOG_DEBUG, "Issuing AKILL: *@#{host}, #{reason} lasting for #{@akill_duration} seconds") + ret = akill_add("*@#{host}", reason, @akill_duration) + end # if protected hosts end # if host channel.downcase! @@ -596,6 +666,31 @@ def akill(client, reason, operreason, channel="") end # if kill_user end # def akill +# ------------------------------------------------------------------------ + + # convert irc pattern to regular expression + def irc_pattern_to_regex(pattern + # "." -> "\.", "*" -> ".*", "?" -> "." + # wrap with "^...$" + return pattern if pattern.start_with? '^' + pattern = pattern.gsub(/\./, '\\.') + .gsub(/\*/, '.*') + .gsub(/\?/, '.') + return "^#{pattern}$" + end + +# ------------------------------------------------------------------------ + + # get protected patterns as pattern + def load_protected_patterns() + patterns = @protection.keys + if patterns.empty? + @protected_patterns = '^$' + else + @protected_patterns = patterns.join('|') + end + end # def get_protected_patterns + # ------------------------------------------------------------------------ # enforce a channel - kill all of its users @@ -648,6 +743,20 @@ def load_data() count += 1 } result.free + + @protection = Hash.new + result = DB.execute(@dbq['GET_PROTECTED_PATTERNS'], '') + count = 0 + result.row_each { |row| + pattern = row[0] + reason = row[1] + pattern = irc_pattern_to_regex(pattern) + @protection[pattern] = reason + count += 1 + } + result.free + load_protected_patterns + debug(LOG_DEBUG, "All channel data successfully loaded") end # def load_data diff --git a/sql/ganneffserv-pgsql.sql b/sql/ganneffserv-pgsql.sql index 9f8feb8..75f4d56 100644 --- a/sql/ganneffserv-pgsql.sql +++ b/sql/ganneffserv-pgsql.sql @@ -8,4 +8,14 @@ CREATE TABLE ganneffserv ( kills INTEGER NOT NULL DEFAULT 0, monitor_only BOOLEAN NOT NULL DEFAULT 'False' ); + +DROP TABLE IF EXISTS ganneffprotect CASCADE; +CREATE TABLE ganneffprotect ( + id SERIAL PRIMARY KEY, + setter INTEGER REFERENCES account(id) ON DELETE SET NULL, + time INTEGER NOT NULL, + pattern VARCHAR(255) NOT NULL, + reason VARCHAR(255) NOT NULL +) + CREATE UNIQUE INDEX ganneffserv_channel_idx ON ganneffserv (irc_lower(channel)); From 0aabdd13641b2827ead4119f9046f09d21c37213 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 14 Nov 2021 20:33:05 -0500 Subject: [PATCH 3/3] Put the table creation below all the existing schema Add a unique index so Postgres will enforce unique constraints Co-authored-by: Doug Freed --- sql/ganneffserv-pgsql.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/ganneffserv-pgsql.sql b/sql/ganneffserv-pgsql.sql index 75f4d56..e24fe19 100644 --- a/sql/ganneffserv-pgsql.sql +++ b/sql/ganneffserv-pgsql.sql @@ -8,6 +8,7 @@ CREATE TABLE ganneffserv ( kills INTEGER NOT NULL DEFAULT 0, monitor_only BOOLEAN NOT NULL DEFAULT 'False' ); +CREATE UNIQUE INDEX ganneffserv_channel_idx ON ganneffserv (irc_lower(channel)); DROP TABLE IF EXISTS ganneffprotect CASCADE; CREATE TABLE ganneffprotect ( @@ -16,6 +17,5 @@ CREATE TABLE ganneffprotect ( time INTEGER NOT NULL, pattern VARCHAR(255) NOT NULL, reason VARCHAR(255) NOT NULL -) - -CREATE UNIQUE INDEX ganneffserv_channel_idx ON ganneffserv (irc_lower(channel)); +); +CREATE UNIQUE INDEX ganneffservprotect_pattern_idx ON ganneffservprotect (irc_lower(pattern));