3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-12-27 13:12:42 +01:00
pbot/lib/PBot/BanList.pm

780 lines
30 KiB
Perl
Raw Normal View History

# File: BanList.pm
2011-02-13 06:07:02 +01:00
#
# Purpose: Populates and maintains channel banlists by checking mode +b/+q on
# joining channels and by tracking modes +b/+q and -b/-q in channels. Keeps
# track of remaining duration for timed bans/quiets. Handles ban/unban queue.
2011-02-13 06:07:02 +01:00
2021-07-11 00:00:22 +02:00
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
License project under MPL2 This patch adds the file LICENSE which is the verbatim copy of the Mozilla Public License Version 2.0 as retreived from https://www.mozilla.org/media/MPL/2.0/index.815ca599c9df.txt on 2017-03-05. This patch also places license headers for the MPL2 type A variant of the license header in the following files: PBot/AntiFlood.pm PBot/BanTracker.pm PBot/BlackList.pm PBot/BotAdminCommands.pm PBot/BotAdmins.pm PBot/ChanOpCommands.pm PBot/ChanOps.pm PBot/Channels.pm PBot/Commands.pm PBot/DualIndexHashObject.pm PBot/EventDispatcher.pm PBot/FactoidCommands.pm PBot/FactoidModuleLauncher.pm PBot/Factoids.pm PBot/HashObject.pm PBot/IRCHandlers.pm PBot/IgnoreList.pm PBot/IgnoreListCommands.pm PBot/Interpreter.pm PBot/LagChecker.pm PBot/Logger.pm PBot/MessageHistory.pm PBot/MessageHistory_SQLite.pm PBot/NickList.pm PBot/PBot.pm PBot/Plugins.pm PBot/Plugins/AntiAway.pm PBot/Plugins/AntiKickAutoRejoin.pm PBot/Plugins/AntiRepeat.pm PBot/Plugins/AntiTwitter.pm PBot/Plugins/AutoRejoin.pm PBot/Plugins/Counter.pm PBot/Plugins/Quotegrabs.pm PBot/Plugins/Quotegrabs/Quotegrabs_Hashtable.pm PBot/Plugins/Quotegrabs/Quotegrabs_SQLite.pm PBot/Plugins/UrlTitles.pm PBot/Plugins/_Example.pm PBot/Refresher.pm PBot/Registerable.pm PBot/Registry.pm PBot/RegistryCommands.pm PBot/SQLiteLogger.pm PBot/SQLiteLoggerLayer.pm PBot/SelectHandler.pm PBot/StdinReader.pm PBot/Timer.pm PBot/Utils/ParseDate.pm PBot/VERSION.pm build/update-version.pl modules/acronym.pl modules/ago.pl modules/c11std.pl modules/c2english.pl modules/c2english/CGrammar.pm modules/c2english/c2eng.pl modules/c99std.pl modules/cdecl.pl modules/cfaq.pl modules/cjeopardy/IRCColors.pm modules/cjeopardy/QStatskeeper.pm modules/cjeopardy/Scorekeeper.pm modules/cjeopardy/cjeopardy.pl modules/cjeopardy/cjeopardy_answer.pl modules/cjeopardy/cjeopardy_filter.pl modules/cjeopardy/cjeopardy_hint.pl modules/cjeopardy/cjeopardy_qstats.pl modules/cjeopardy/cjeopardy_scores.pl modules/cjeopardy/cjeopardy_show.pl modules/codepad.pl modules/compiler_block.pl modules/compiler_client.pl modules/compiler_vm/Diff.pm modules/compiler_vm/cc modules/compiler_vm/compiler_client.pl modules/compiler_vm/compiler_server.pl modules/compiler_vm/compiler_server_vbox_win32.pl modules/compiler_vm/compiler_server_watchdog.pl modules/compiler_vm/compiler_vm_client.pl modules/compiler_vm/compiler_vm_server.pl modules/compiler_vm/compiler_watchdog.pl modules/compiler_vm/languages/_c_base.pm modules/compiler_vm/languages/_default.pm modules/compiler_vm/languages/bash.pm modules/compiler_vm/languages/bc.pm modules/compiler_vm/languages/bf.pm modules/compiler_vm/languages/c11.pm modules/compiler_vm/languages/c89.pm modules/compiler_vm/languages/c99.pm modules/compiler_vm/languages/clang.pm modules/compiler_vm/languages/clang11.pm modules/compiler_vm/languages/clang89.pm modules/compiler_vm/languages/clang99.pm modules/compiler_vm/languages/clangpp.pm modules/compiler_vm/languages/clisp.pm modules/compiler_vm/languages/cpp.pm modules/compiler_vm/languages/freebasic.pm modules/compiler_vm/languages/go.pm modules/compiler_vm/languages/haskell.pm modules/compiler_vm/languages/java.pm modules/compiler_vm/languages/javascript.pm modules/compiler_vm/languages/ksh.pm modules/compiler_vm/languages/lua.pm modules/compiler_vm/languages/perl.pm modules/compiler_vm/languages/python.pm modules/compiler_vm/languages/python3.pm modules/compiler_vm/languages/qbasic.pm modules/compiler_vm/languages/scheme.pm modules/compiler_vm/languages/server/_c_base.pm modules/compiler_vm/languages/server/_default.pm modules/compiler_vm/languages/server/c11.pm modules/compiler_vm/languages/server/c89.pm modules/compiler_vm/languages/server/c99.pm modules/compiler_vm/languages/server/clang.pm modules/compiler_vm/languages/server/clang11.pm modules/compiler_vm/languages/server/clang89.pm modules/compiler_vm/languages/server/clang99.pm modules/compiler_vm/languages/server/cpp.pm modules/compiler_vm/languages/server/freebasic.pm modules/compiler_vm/languages/server/haskell.pm modules/compiler_vm/languages/server/java.pm modules/compiler_vm/languages/server/qbasic.pm modules/compiler_vm/languages/server/tendra.pm modules/compiler_vm/languages/sh.pm modules/compiler_vm/languages/tendra.pm modules/compliment modules/cstd.pl modules/define.pl modules/dice_roll.pl modules/excuse.sh modules/expand_macros.pl modules/fnord.pl modules/funnyish_quote.pl modules/g.pl modules/gdefine.pl modules/gen_cfacts.pl modules/gencstd.pl modules/get_title.pl modules/getcfact.pl modules/google.pl modules/gspy.pl modules/gtop10.pl modules/gtop15.pl modules/headlines.pl modules/horoscope modules/horrorscope modules/ideone.pl modules/insult.pl modules/love_quote.pl modules/man.pl modules/map.pl modules/math.pl modules/prototype.pl modules/qalc.pl modules/random_quote.pl modules/seen.pl modules/urban modules/weather.pl modules/wikipedia.pl pbot.pl pbot.sh It is highly recommended that this list of files is reviewed to ensure that all files are the copyright of the sole maintainer of the repository. If any files with license headers contain the intellectual property of anyone else, it is recommended that a request is made to revise this patch or that the explicit permission of the co-author is gained to allow for the license of the work to be changed. I (Tomasz Kramkowski), the contributor, take no responsibility for any legal action taken against the maintainer of this repository for incorrectly claiming copyright to any work not owned by the maintainer of this repository.
2017-03-05 22:33:31 +01:00
package PBot::BanList;
2020-02-15 23:38:32 +01:00
use parent 'PBot::Class';
2011-02-13 06:07:02 +01:00
2021-06-19 06:23:34 +02:00
use PBot::Imports;
2019-07-11 03:40:53 +02:00
2011-02-13 06:07:02 +01:00
use Time::HiRes qw/gettimeofday/;
use Time::Duration;
use POSIX qw/strftime/;
2020-02-15 23:38:32 +01:00
2011-02-13 06:07:02 +01:00
sub initialize {
2020-02-15 23:38:32 +01:00
my ($self, %conf) = @_;
$self->{pbot}->{registry}->add_default('text', 'banlist', 'chanserv_ban_timeout', '604800');
$self->{pbot}->{registry}->add_default('text', 'banlist', 'mute_timeout', '604800');
$self->{pbot}->{registry}->add_default('text', 'banlist', 'debug', '0');
$self->{pbot}->{registry}->add_default('text', 'banlist', 'mute_mode_char', 'q');
$self->{pbot}->{commands}->register(sub { $self->cmd_banlist(@_) }, "banlist", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_checkban(@_) }, "checkban", 0);
$self->{pbot}->{commands}->register(sub { $self->cmd_checkmute(@_) }, "checkmute", 0);
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofnames', sub { $self->get_banlist(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.banlist', sub { $self->on_banlist_entry(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.quietlist', sub { $self->on_quietlist_entry(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofbanlist', sub { $self->compare_banlist(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('irc.endofquietlist', sub { $self->compare_quietlist(@_) });
$self->{banlist} = PBot::DualIndexHashObject->new(
pbot => $self->{pbot},
name => 'Ban List',
filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/banlist',
save_queue_timeout => 15,
);
$self->{quietlist} = PBot::DualIndexHashObject->new(
pbot => $self->{pbot},
name => 'Quiet List',
filename => $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/quietlist',
save_queue_timeout => 15,
);
$self->{banlist}->load;
$self->{quietlist}->load;
$self->enqueue_timeouts($self->{banlist}, 'b');
$self->enqueue_timeouts($self->{quietlist}, $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char'));
$self->{ban_queue} = {};
$self->{unban_queue} = {};
$self->{pbot}->{event_queue}->enqueue(sub { $self->flush_unban_queue }, 30, 'Flush unban queue');
2011-02-13 06:07:02 +01:00
}
sub cmd_banlist {
my ($self, $context) = @_;
if (not length $context->{arguments}) {
return "Usage: banlist <channel>";
}
my $result = "Ban list for $context->{arguments}:\n";
if ($self->{banlist}->exists($context->{arguments})) {
my $count = $self->{banlist}->get_keys($context->{arguments});
2020-04-29 07:36:13 +02:00
$result .= "$count ban" . ($count == 1 ? '' : 's') . ":\n";
foreach my $mask ($self->{banlist}->get_keys($context->{arguments})) {
my $data = $self->{banlist}->get_data($context->{arguments}, $mask);
$result .= " $mask banned ";
if (defined $data->{timestamp}) {
my $date = strftime "%a %b %e %H:%M:%S %Y %Z", localtime $data->{timestamp};
my $ago = concise ago (time - $data->{timestamp});
$result .= "on $date ($ago) ";
}
$result .= "by $data->{owner} " if defined $data->{owner};
$result .= "for $data->{reason} " if defined $data->{reason};
if (defined $data->{timeout} and $data->{timeout} > 0) {
my $duration = concise duration($data->{timeout} - gettimeofday);
$result .= "($duration remaining)";
}
$result .= ";\n";
}
} else {
$result .= "bans: none;\n";
}
if ($self->{quietlist}->exists($context->{arguments})) {
my $count = $self->{quietlist}->get_keys($context->{arguments});
2020-04-29 07:36:13 +02:00
$result .= "$count mute" . ($count == 1 ? '' : 's') . ":\n";
foreach my $mask ($self->{quietlist}->get_keys($context->{arguments})) {
my $data = $self->{quietlist}->get_data($context->{arguments}, $mask);
$result .= " $mask muted ";
if (defined $data->{timestamp}) {
my $date = strftime "%a %b %e %H:%M:%S %Y %Z", localtime $data->{timestamp};
my $ago = concise ago (time - $data->{timestamp});
2020-04-29 07:43:13 +02:00
$result .= "on $date ($ago) ";
}
$result .= "by $data->{owner} " if defined $data->{owner};
$result .= "for $data->{reason} " if defined $data->{reason};
if (defined $data->{timeout} and $data->{timeout} > 0) {
my $duration = concise duration($data->{timeout} - gettimeofday);
$result .= "($duration remaining)";
}
$result .= ";\n";
}
} else {
2020-04-29 07:36:13 +02:00
$result .= "quiets: none\n";
}
2020-04-29 07:36:13 +02:00
$result =~ s/ ;/;/g;
return $result;
}
sub cmd_checkban {
my ($self, $context) = @_;
2020-05-02 05:59:51 +02:00
my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 2);
return "Usage: checkban <mask> [channel]" if not defined $target;
$channel = $context->{from} if not defined $channel;
return "Please specify a channel." if $channel !~ /^#/;
return $self->checkban($channel, 'b', $target);
}
sub cmd_checkmute {
my ($self, $context) = @_;
2020-05-02 05:59:51 +02:00
my ($target, $channel) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 2);
return "Usage: checkmute <mask> [channel]" if not defined $target;
$channel = $context->{from} if not defined $channel;
return "Please specify a channel." if $channel !~ /^#/;
return $self->checkban($channel, $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char'), $target);
2011-02-13 06:07:02 +01:00
}
sub get_banlist {
2020-02-15 23:38:32 +01:00
my ($self, $event_type, $event) = @_;
my $channel = lc $event->{event}->{args}[1];
$self->{pbot}->{logger}->log("Retrieving banlist for $channel.\n");
delete $self->{temp_banlist};
my $mute_char = $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char');
if ($mute_char eq 'b') {
$event->{conn}->sl("mode $channel +b");
} else {
$event->{conn}->sl("mode $channel +b$mute_char");
}
2020-02-15 23:38:32 +01:00
return 0;
}
sub on_banlist_entry {
2020-02-15 23:38:32 +01:00
my ($self, $event_type, $event) = @_;
2020-02-15 23:38:32 +01:00
my $channel = lc $event->{event}->{args}[1];
my $target = lc $event->{event}->{args}[2];
my $source = lc $event->{event}->{args}[3];
my $timestamp = $event->{event}->{args}[4];
my $ago = concise ago(gettimeofday - $timestamp);
2020-04-29 07:36:13 +02:00
$self->{pbot}->{logger}->log("Ban List: [banlist entry] $channel: $target banned by $source $ago.\n");
$self->{temp_banlist}->{$channel}->{'+b'}->{$target} = [$source, $timestamp];
return 0;
}
sub on_quietlist_entry {
my ($self, $event_type, $event) = @_;
my $channel = lc $event->{event}->{args}[1];
my $target = lc $event->{event}->{args}[3];
my $source = lc $event->{event}->{args}[4];
my $timestamp = $event->{event}->{args}[5];
my $ago = concise ago(gettimeofday - $timestamp);
2020-04-29 07:36:13 +02:00
$self->{pbot}->{logger}->log("Ban List: [quietlist entry] $channel: $target quieted by $source $ago.\n");
my $mute_char = $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char');
$self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$target} = [$source, $timestamp];
return 0;
}
sub compare_banlist {
my ($self, $event_type, $event) = @_;
my $channel = lc $event->{event}->{args}[1];
# first check for saved bans no longer in channel
foreach my $mask ($self->{banlist}->get_keys($channel)) {
if (not exists $self->{temp_banlist}->{$channel}->{'+b'}->{$mask}) {
$self->{pbot}->{logger}->log("BanList: Saved ban +b $mask no longer exists in $channel.\n");
# TODO option to restore ban
$self->{banlist}->remove($channel, $mask, undef, 1);
$self->{pbot}->{event_queue}->dequeue_event("unban $channel $mask");
}
}
# add channel bans to saved bans
foreach my $mask (keys %{$self->{temp_banlist}->{$channel}->{'+b'}}) {
my $data = $self->{banlist}->get_data($channel, $mask);
$data->{owner} = $self->{temp_banlist}->{$channel}->{'+b'}->{$mask}->[0];
$data->{timestamp} = $self->{temp_banlist}->{$channel}->{'+b'}->{$mask}->[1];
# make some special-case bans temporary
if (not defined $data->{timeout} and $self->{pbot}->{chanops}->can_gain_ops($channel)) {
if ($mask =~ m/^\*!\*@/ or $mask =~ m/^\*!.*\@gateway\/web/i) {
my $timeout = 60 * 60 * 24 * 7;
# permanent bans for cloaks that aren't gateway
$timeout = 0 if $mask =~ m/\// and $mask !~ m/\@gateway/;
if ($timeout) {
$self->{pbot}->{logger}->log("Temp ban for $mask in $channel.\n");
$data->{timeout} = gettimeofday + $timeout;
2020-10-23 09:22:15 +02:00
$self->enqueue_unban($channel, 'b', $mask, $timeout);
}
}
}
$self->{banlist}->add($channel, $mask, $data, 1);
}
2020-04-29 07:36:13 +02:00
$self->{banlist}->save if keys %{$self->{temp_banlist}->{$channel}->{'+b'}};
delete $self->{temp_banlist}->{$channel}->{'+b'};
}
sub compare_quietlist {
my ($self, $event_type, $event) = @_;
my $channel = lc $event->{event}->{args}[1];
my $mute_char = $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char');
# first check for saved quiets no longer in channel
foreach my $mask ($self->{quietlist}->get_keys($channel)) {
if (not exists $self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$mask}) {
$self->{pbot}->{logger}->log("BanList: Saved quiet +q $mask no longer exists in $channel.\n");
# TODO option to restore quiet
$self->{quietlist}->remove($channel, $mask, undef, 1);
$self->{pbot}->{event_queue}->dequeue_event("unmute $channel $mask");
}
}
# add channel bans to saved bans
foreach my $mask (keys %{$self->{temp_banlist}->{$channel}->{"+$mute_char"}}) {
my $data = $self->{quietlist}->get_data($channel, $mask);
$data->{owner} = $self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$mask}->[0];
$data->{timestamp} = $self->{temp_banlist}->{$channel}->{"+$mute_char"}->{$mask}->[1];
$self->{quietlist}->add($channel, $mask, $data, 1);
}
$self->{quietlist}->save if keys %{$self->{temp_banlist}->{$channel}->{"+$mute_char"}};
delete $self->{temp_banlist}->{$channel}->{"+$mute_char"};
}
sub track_mode {
my $self = shift;
my ($source, $channel, $mode, $mask) = @_;
my ($nick) = $source =~ /(^[^!]+)/;
$channel = defined $channel ? lc $channel : '';
$mask = defined $mask ? lc $mask : '';
my $mute_char = $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char');
if ($mode eq "+b" or $mode eq "+$mute_char") {
$self->{pbot}->{logger}->log("Ban List: $mask " . ($mode eq '+b' ? 'banned' : 'muted') . " by $source in $channel.\n");
my $data = {
owner => $source,
timestamp => scalar gettimeofday,
};
2020-02-15 23:38:32 +01:00
if ($mode eq "+b") {
$self->{banlist}->add($channel, $mask, $data);
} elsif ($mode eq "+$mute_char") {
$self->{quietlist}->add($channel, $mask, $data);
2020-02-15 23:38:32 +01:00
}
$self->{pbot}->{antiflood}->devalidate_accounts($mask, $channel);
} elsif ($mode eq "-b" or $mode eq "-$mute_char") {
$self->{pbot}->{logger}->log("Ban List: $mask " . ($mode eq '-b' ? 'unbanned' : 'unmuted') . " by $source in $channel.\n");
if ($mode eq "-b") {
$self->{banlist}->remove($channel, $mask);
$self->{pbot}->{event_queue}->dequeue_event("unban $channel $mask");
# freenode strips channel forwards from unban result if no ban exists with a channel forward
my $join_flood_channel = $self->{pbot}->{registry}->get_value('antiflood', 'join_flood_channel') // '#stop-join-flood';
$self->{banlist}->remove($channel, "$mask\$$join_flood_channel");
$self->{pbot}->{event_queue}->dequeue_event(lc "unban $channel $mask\$$join_flood_channel");
} elsif ($mode eq "-$mute_char") {
$self->{quietlist}->remove($channel, $mask);
$self->{pbot}->{event_queue}->dequeue_event("unmute $channel $mask");
}
}
return if not $self->{pbot}->{chanops}->can_gain_ops($channel);
if ($mode eq "+b") {
if ($nick eq "ChanServ" or $mask =~ m/##fix_your_connection$/i) {
if ($self->{banlist}->exists($channel, $mask)) {
$self->{banlist}->set($channel, $mask, 'timeout', gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
$self->{pbot}->{event_queue}->update_interval("unban $channel $mask", $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
} else {
2020-02-15 23:38:32 +01:00
my $data = {
reason => 'Temp ban for banned-by-ChanServ or mask is *!*@*##fix_your_connection',
owner => $self->{pbot}->{registry}->get_value('irc', 'botnick'),
timeout => gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'),
timestamp => gettimeofday,
2020-02-15 23:38:32 +01:00
};
$self->{banlist}->add($channel, $mask, $data);
$self->enqueue_unban($channel, 'b', $mask, $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
}
} elsif ($mask =~ m/^\*!\*@/ or $mask =~ m/^\*!.*\@gateway\/web/i) {
my $timeout = 60 * 60 * 24 * 7;
if ($mask =~ m/\// and $mask !~ m/\@gateway/) {
$timeout = 0; # permanent bans for cloaks that aren't gateway
}
if ($timeout) {
if (not $self->{banlist}->exists($channel, $mask)) {
$self->{pbot}->{logger}->log("Temp ban for $mask in $channel.\n");
my $data = {
2020-04-29 07:36:13 +02:00
reason => 'Temp ban for *!*@host',
timeout => gettimeofday + $timeout,
owner => $self->{pbot}->{registry}->get_value('irc', 'botnick'),
timestamp => gettimeofday,
};
$self->{banlist}->add($channel, $mask, $data);
$self->enqueue_unban($channel, 'b', $mask, $timeout);
}
}
}
} elsif ($mode eq "+$mute_char") {
if (lc $nick ne lc $self->{pbot}->{registry}->get_value('irc', 'botnick')) {
$self->{pbot}->{logger}->log("WEIRD MUTE THING $nick...\n");
if ($self->{quietlist}->exists($channel, $mask)) {
$self->{quietlist}->set($channel, $mask, 'timeout', gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
$self->{pbot}->{event_queue}->update_interval("unmute $channel $mask", $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
} else {
my $data = {
reason => 'Temp mute',
owner => $self->{pbot}->{registry}->get_value('irc', 'botnick'),
timeout => gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'mute_timeout'),
timestamp => gettimeofday,
};
$self->{quietlist}->add($channel, $mask, $data);
$self->enqueue_unban($channel, $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char'), $mask, $self->{pbot}->{registry}->get_value('banlist', 'mute_timeout'));
2020-02-15 23:38:32 +01:00
}
}
}
}
sub ban_user {
my ($self, $channel, $mode, $mask, $immediately) = @_;
$mode ||= 'b';
$self->{pbot}->{logger}->log("Banning $channel +$mode $mask\n");
$self->add_to_ban_queue($channel, $mode, $mask);
if (not defined $immediately or $immediately != 0) {
$self->flush_ban_queue;
}
}
2020-02-15 23:38:32 +01:00
sub unban_user {
my ($self, $channel, $mode, $mask, $immediately) = @_;
$mask = lc $mask;
$channel = lc $channel;
$mode ||= 'b';
$self->{pbot}->{logger}->log("Unbanning $channel -$mode $mask\n");
$self->unmode_user($channel, $mode, $mask, $immediately);
}
2020-02-15 23:38:32 +01:00
sub unmode_user {
my ($self, $channel, $mode, $mask, $immediately) = @_;
$mask = lc $mask;
$channel = lc $channel;
$self->{pbot}->{logger}->log("Removing mode $mode from $mask in $channel\n");
my $bans = $self->get_bans($channel, $mask);
my %unbanned;
if (not defined $bans) {
push @$bans, { mask => $mask, type => $mode };
}
foreach my $ban (@$bans) {
next if $ban->{type} ne $mode;
2020-04-29 07:36:13 +02:00
next if exists $unbanned{$ban->{mask}};
$unbanned{$ban->{mask}} = 1;
$self->add_to_unban_queue($channel, $mode, $ban->{mask});
}
$self->flush_unban_queue if $immediately;
}
sub get_bans {
my ($self, $channel, $mask) = @_;
my $masks;
my ($message_account, $hostmask);
if ($mask !~ m/[!@]/) {
($message_account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($mask);
$hostmask = $mask if not defined $message_account;
} else {
$message_account = $self->{pbot}->{messagehistory}->{database}->get_message_account_id($mask);
$hostmask = $mask;
}
if (defined $message_account) {
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account);
$masks = $self->get_baninfo($channel, $hostmask, $nickserv);
}
my %akas = $self->{pbot}->{messagehistory}->{database}->get_also_known_as($hostmask);
foreach my $aka (keys %akas) {
next if $akas{$aka}->{type} == $self->{pbot}->{messagehistory}->{database}->{alias_type}->{WEAK};
next if $akas{$aka}->{nickchange} == 1;
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($akas{$aka}->{id});
my $b = $self->get_baninfo($channel, $aka, $nickserv);
if (defined $b) {
push @$masks, @$b;
}
}
return $masks;
}
sub get_baninfo {
my ($self, $channel, $mask, $nickserv) = @_;
my ($bans, $ban_nickserv);
2013-07-28 12:31:12 +02:00
$nickserv = undef if not length $nickserv;
$nickserv = lc $nickserv if defined $nickserv;
2013-07-30 15:12:21 +02:00
if ($self->{pbot}->{registry}->get_value('banlist', 'debug')) {
my $ns = defined $nickserv ? $nickserv : "[undefined]";
$self->{pbot}->{logger}->log("[get-baninfo] Getting baninfo for $mask in $channel using nickserv $ns\n");
2020-02-15 23:38:32 +01:00
}
2013-08-03 19:26:49 +02:00
2020-02-15 23:38:32 +01:00
my ($nick, $user, $host) = $mask =~ m/([^!]+)!([^@]+)@(.*)/;
my @lists = (
[ 'b', $self->{banlist} ],
[ $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char'), $self->{quietlist} ],
);
2013-07-28 12:31:12 +02:00
foreach my $entry (@lists) {
my ($mode, $list) = @$entry;
foreach my $banmask ($list->get_keys($channel)) {
if ($banmask =~ m/^\$a:(.*)/) { $ban_nickserv = lc $1; }
else { $ban_nickserv = ""; }
my $banmask_regex = quotemeta $banmask;
$banmask_regex =~ s/\\\*/.*?/g;
$banmask_regex =~ s/\\\?/./g;
my $banned;
$banned = 1 if defined $nickserv and $nickserv eq $ban_nickserv;
$banned = 1 if $mask =~ m/^$banmask_regex$/i;
if ($banmask =~ m{\@gateway/web/irccloud.com} and $host =~ m{^gateway/web/irccloud.com}) {
my ($bannick, $banuser, $banhost) = $banmask =~ m/([^!]+)!([^@]+)@(.*)/;
$banned = $1 if lc $user eq lc $banuser;
2020-02-15 23:38:32 +01:00
}
2020-02-15 23:38:32 +01:00
if ($banned) {
my $data = $list->get_data($channel, $banmask);
my $baninfo = {
mask => $banmask,
channel => $channel,
owner => $data->{owner},
when => $data->{timestamp},
type => $mode,
reason => $data->{reason},
timeout => $data->{timeout},
};
2020-02-15 23:38:32 +01:00
push @$bans, $baninfo;
}
}
}
2020-02-15 23:38:32 +01:00
return $bans;
2011-02-13 06:07:02 +01:00
}
sub nick_to_banmask {
my ($self, $mask) = @_;
if ($mask !~ m/[!@\$]/) {
my ($message_account, $hostmask) = $self->{pbot}->{messagehistory}->{database}->find_message_account_by_nick($mask);
if (defined $hostmask) {
my $nickserv = $self->{pbot}->{messagehistory}->{database}->get_current_nickserv_account($message_account);
if (defined $nickserv && length $nickserv) { $mask = '$a:' . $nickserv; }
else {
my ($nick, $user, $host) = $hostmask =~ m/([^!]+)!([^@]+)@(.*)/;
$mask = "*!$user\@" . $self->{pbot}->{antiflood}->address_to_mask($host);
}
} else {
$mask .= '!*@*';
}
}
# make sure $mask always has full wildcards
# there's probably a better way to do this...
if ($mask !~ /^\$/) {
if ($mask !~ /!/) {
$mask .= '!*@*';
} elsif ($mask !~ /@/) {
$mask =~ s/\*?$/*@*/;
} else {
$mask =~ s/\@$/@*/;
}
}
return $mask;
}
sub ban_user_timed {
my ($self, $channel, $mode, $mask, $length, $owner, $reason, $immediately) = @_;
$channel = lc $channel;
$mask = lc $mask;
$mask = $self->nick_to_banmask($mask);
$self->ban_user($channel, $mode, $mask, $immediately);
my $data = {
timeout => $length > 0 ? gettimeofday + $length : -1,
owner => $owner,
reason => $reason,
timestamp => time,
};
if ($mode eq 'b') {
$self->{banlist}->remove($channel, $mask, 'timeout');
$self->{banlist}->add($channel, $mask, $data);
} elsif ($mode eq $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char')) {
$self->{quietlist}->remove($channel, $mask, 'timeout');
$self->{quietlist}->add($channel, $mask, $data);
}
my $method = $mode eq 'b' ? 'unban' : 'unmute';
$self->{pbot}->{event_queue}->dequeue_event("$method $channel $mask");
if ($length > 0) {
$self->enqueue_unban($channel, $mode, $mask, $length);
}
}
sub checkban {
my ($self, $channel, $mode, $mask) = @_;
$mask = $self->nick_to_banmask($mask);
my $data;
if ($mode eq 'b') {
$data = $self->{banlist}->get_data($channel, $mask);
} elsif ($mode eq $self->{pbot}->{registry}->get_value('banlist', 'mute_mode_char')) {
$data = $self->{quietlist}->get_data($channel, $mask);
}
if (not defined $data) {
return "$mask is not " . ($mode eq 'b' ? 'banned' : 'muted') . ".";
}
my $result = "$mask " . ($mode eq 'b' ? 'banned' : 'quieted') . " in $channel ";
if (defined $data->{timestamp}) {
my $date = strftime "%a %b %e %H:%M:%S %Y %Z", localtime $data->{timestamp};
my $ago = concise ago (time - $data->{timestamp});
2020-04-29 07:43:13 +02:00
$result .= "on $date ($ago) ";
}
$result .= "by $data->{owner} " if defined $data->{owner};
$result .= "for $data->{reason} " if defined $data->{reason};
if (exists $data->{timeout} and $data->{timeout} > 0) {
my $duration = concise duration($data->{timeout} - gettimeofday);
$result .= "($duration remaining)";
}
return $result;
}
sub add_to_ban_queue {
my ($self, $channel, $mode, $mask) = @_;
if (not grep { $_ eq $mask } @{$self->{ban_queue}->{$channel}->{$mode}}) {
push @{$self->{ban_queue}->{$channel}->{$mode}}, $mask;
$self->{pbot}->{logger}->log("Added +$mode $mask for $channel to ban queue.\n");
}
}
sub flush_ban_queue {
my $self = shift;
my $MAX_COMMANDS = 4;
my $commands = 0;
foreach my $channel (keys %{$self->{ban_queue}}) {
my $done = 0;
while (not $done) {
my ($list, $count, $modes);
$list = '';
$modes = '+';
$count = 0;
foreach my $mode (keys %{$self->{ban_queue}->{$channel}}) {
while (@{$self->{ban_queue}->{$channel}->{$mode}}) {
my $target = pop @{$self->{ban_queue}->{$channel}->{$mode}};
$list .= " $target";
$modes .= $mode;
last if ++$count >= $self->{pbot}->{isupport}->{MODES} // 1;
}
if (not @{$self->{ban_queue}->{$channel}->{$mode}}) {
delete $self->{ban_queue}->{$channel}->{$mode};
}
last if $count >= $self->{pbot}->{isupport}->{MODES} // 1;
}
if (not keys %{$self->{ban_queue}->{$channel}}) {
delete $self->{ban_queue}->{$channel};
$done = 1;
}
if ($count) {
$self->{pbot}->{chanops}->add_op_command($channel, "mode $channel $modes $list");
$self->{pbot}->{chanops}->gain_ops($channel);
return if ++$commands >= $MAX_COMMANDS;
}
}
}
}
sub add_to_unban_queue {
my ($self, $channel, $mode, $mask) = @_;
if (not grep { $_ eq $mask } @{$self->{unban_queue}->{$channel}->{$mode}}) {
push @{$self->{unban_queue}->{$channel}->{$mode}}, $mask;
$self->{pbot}->{logger}->log("Added -$mode $mask for $channel to unban queue.\n");
}
}
sub flush_unban_queue {
my $self = shift;
my $MAX_COMMANDS = 4;
my $commands = 0;
foreach my $channel (keys %{$self->{unban_queue}}) {
my $done = 0;
while (not $done) {
my ($list, $count, $modes);
$list = '';
$modes = '-';
$count = 0;
foreach my $mode (keys %{$self->{unban_queue}->{$channel}}) {
while (@{$self->{unban_queue}->{$channel}->{$mode}}) {
my $target = pop @{$self->{unban_queue}->{$channel}->{$mode}};
$list .= " $target";
$modes .= $mode;
last if ++$count >= $self->{pbot}->{isupport}->{MODES} // 1;
}
if (not @{$self->{unban_queue}->{$channel}->{$mode}}) {
delete $self->{unban_queue}->{$channel}->{$mode};
}
last if $count >= $self->{pbot}->{isupport}->{MODES} // 1;
}
if (not keys %{$self->{unban_queue}->{$channel}}) {
delete $self->{unban_queue}->{$channel};
$done = 1;
}
if ($count) {
$self->{pbot}->{chanops}->add_op_command($channel, "mode $channel $modes $list");
$self->{pbot}->{chanops}->gain_ops($channel);
return if ++$commands >= $MAX_COMMANDS;
}
}
}
}
sub enqueue_unban {
my ($self, $channel, $mode, $hostmask, $interval) = @_;
my $method = $mode eq 'b' ? 'unban' : 'unmute';
$self->{pbot}->{event_queue}->enqueue_event(
sub {
$self->{pbot}->{event_queue}->update_interval("$method $channel $hostmask", 60 * 15, 1); # try again in 15 minutes
return if not $self->{pbot}->{joined_channels};
$self->unban_user($channel, $mode, $hostmask);
}, $interval, "$method $channel $hostmask", 1
);
}
sub enqueue_timeouts {
my ($self, $list, $mode) = @_;
my $now = time;
foreach my $channel ($list->get_keys) {
foreach my $mask ($list->get_keys($channel)) {
my $timeout = $list->get_data($channel, $mask, 'timeout');
2020-04-29 07:36:13 +02:00
next if defined $timeout and $timeout <= 0;
next if not defined $timeout;
my $interval = $timeout - $now;
$interval = 10 if $interval < 10;
$self->enqueue_unban($channel, $mode, $mask, $interval);
}
}
}
sub has_ban_timeout {
my ($self, $channel, $mask, $mode) = @_;
$mode ||= 'b';
my $list = $mode eq 'b' ? $self->{banlist} : $self->{quietlist};
my $data = $list->get_data($channel, $mask);
if (defined $data && $data->{timeout} > 0) {
return 1;
} else {
return 0;
}
}
sub is_banned {
my ($self, $channel, $nick, $user, $host) = @_;
2020-02-15 23:38:32 +01:00
my $message_account = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host);
my @nickserv_accounts = $self->{pbot}->{messagehistory}->{database}->get_nickserv_accounts($message_account);
push @nickserv_accounts, undef;
my $banned = undef;
foreach my $nickserv_account (@nickserv_accounts) {
my $baninfos = $self->get_baninfo($channel, "$nick!$user\@$host", $nickserv_account);
2020-02-15 23:38:32 +01:00
if (defined $baninfos) {
foreach my $baninfo (@$baninfos) {
my $u = $self->{pbot}->{users}->loggedin($channel, "$nick!$user\@$host");
my $whitelisted = $self->{pbot}->{capabilities}->userhas($u, 'is-whitelisted');
if ($self->{pbot}->{antiflood}->ban_exempted($baninfo->{channel}, $baninfo->{mask}) || $whitelisted) {
$self->{pbot}->{logger}->log("[BanList] is_banned: $nick!$user\@$host banned as $baninfo->{mask} in $baninfo->{channel}, but allowed through whitelist\n");
2020-02-15 23:38:32 +01:00
} else {
if ($channel eq lc $baninfo->{channel}) {
my $mode = $baninfo->{type} eq 'b' ? "banned" : "quieted";
$self->{pbot}->{logger}->log("[BanList] is_banned: $nick!$user\@$host $mode as $baninfo->{mask} in $baninfo->{channel} by $baninfo->{owner}\n");
2020-02-15 23:38:32 +01:00
$banned = $baninfo;
last;
}
}
}
}
}
return $banned;
2011-02-13 06:07:02 +01:00
}
1;