############################################################################### # badword.pl # Copyright (C) 2002 Jan 'fissie' Sembera # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ############################################################################### # This is configurable badword script. It may be configured to ban immediately # when first badword is detected, or it may count badwords and if number of # badwords of given nick exceeds limit, ban him. Badword count may also be # expired if no badword is seen for specified period of time. Optional # verbosity (let's call it logging) may be enabled as well # # Runtime variables: # # badword_channels = list of channels where script is active, separated by space # badword_words = list of 'bad words' that trigger this, separated by space # badword_reason = reason used in kick when count exceeds permitted limit # badword_limit = if number of detected badwords reaches this number, ban'em. # Set 1 to immediately kickban. # badword_clear_delay = if no badword is detected from user for time specified # here (in seconds), clear his counter. Set 0 to disable. # badword_verbose = turns on/off logging features # badword_ban_delay = ban after number of kicks specified here. 0 - disables # banning, 1 - ban immediately, ... # badword_exemptions = list of hostmasks that is excluded: nick!user@host # badword_warning_1 = a text that is shown (in the channel, prefixed with nick) at first badword # badword_warning_2 = a text that is shown (in the channel, prefixed with nick) at second badword # badword_warning_3 = a text that is shown (in the channel, prefixed with nick) at third badword ############################################################################### # # Changelog: # # Feb 17 2007 [dmp] (dennis@moellegaard.dk) # - new logic to auto-op/deop, # - badword_exemptions (it's global!) # - added a badword_warning_1/2/3 text that is shown first/second/third time typing a badword # Jun 4 2002 # - added ban delaying feature # ############################################################################### use Irssi; use Irssi::Irc; use Data::Dumper; use strict; use vars qw($VERSION %IRSSI); $VERSION = "0.0.3"; %IRSSI = ( authors => "Jan 'fissie' Sembera", contact => "fis\@ji.cz", name => "badword", description => "Configurable badword kickbanning script", license => "GPL v2 and any later", url => "http://fis.bofh.cz/devel/irssi/", ); my %nick_dbase; my %action_queue; sub sig_public { my ($server, $msg, $nick, $address, $target) = @_; my $watch_channels = Irssi::settings_get_str('badword_channels'); my $watch_words = Irssi::settings_get_str('badword_words'); my $exemptions = Irssi::settings_get_str('badword_exemptions'); my @chanz = split (/ /, $watch_channels); my @wordz = split (/ /, $watch_words); my @exemp = split (/ /, $exemptions); my @wordz_instant = split (/ /, Irssi::settings_get_str('badword_words_instantban')); my $nickrec = $server->channel_find($target)->nick_find($nick); my $nickmode = $nickrec->{op} ? "@" : $nickrec->{voice} ? "+" : ""; my $aux = 0; my $verbose = Irssi::settings_get_bool('badword_verbose') == 1; if (! ($nickmode eq "")) { return; } foreach my $e (@exemp) { return if( mask_match("$nick!$address",$e) ); } foreach my $ch (@chanz) { if ($ch eq $target) { $aux = 1; } } if ($aux == 0) { return; } $aux = 0; foreach my $bw_instant (@wordz_instant) { if ($msg =~ /($bw_instant)/i) { my $gotop = $server->channel_find($target)->nick_find($server->{nick})->{op}; my @host = split(/\@/, $address); if (($host[0] =~ /\~/) || ($host[0] =~ /\-/) || ($host[0] =~ /\=/) || ($host[0] =~ /\^/)) { $host[0] = "*"; } my $mask = '*!'.$host[0].'@'.$host[1]; if( $gotop ) { $server->command('mode '.$target.' +b '.$mask); } else { push(@{$action_queue{$target}},'mode '.$target.' +b '.$mask); Irssi::print('BW: Actions pending, trying to op myself in ' . $target) if($verbose); $server->command('quote privmsg chanserv :op ' . $target); } Irssi::print('BW: Nick '.$nick.' with mask '.$mask.' punished for badwording on channel '.$target.' - banned') if($verbose); return; } } my $badword; foreach my $bw (@wordz) { # if (($msg =~ /\ $bw/) || ($msg =~ /^$bw/)) { if ($msg =~ /($bw)/i) { $aux = 1; $badword = $1; } } if ($aux == 0) { return; } # Ok, here comes badword, check record my $luser = $nick_dbase{$nick}{$target}; if (!$luser) { $nick_dbase{$nick}{$target}{'count'} = 1; $nick_dbase{$nick}{$target}{'kcount'} = 0; $nick_dbase{$nick}{$target}{'stamp'} = time(); } else { if ((Irssi::settings_get_int('badword_clear_delay') != 0) && (($nick_dbase{$nick}{$target}{'stamp'})+(Irssi::settings_get_int('badword_clear_delay'))) < time()) { $nick_dbase{$nick}{$target}{'count'} = 1; Irssi::print('BW: Expired for '.$nick.' with hostmask '.$address.' on channel '.$target) if($verbose); } else { $nick_dbase{$nick}{$target}{'count'} = ($nick_dbase{$nick}{$target}{'count'})+1; } $nick_dbase{$nick}{$target}{'stamp'} = time(); } Irssi::print('BW: Detected badword "'.$badword.'" from nick '.$nick.' with hostmask '.$address.' on channel '.$target.' - '.$nick_dbase{$nick}{$target}{'count'}.' times') if($verbose); perform_action($server,$nick,$target,$address) } sub perform_action { my ($server, $nick, $target, $address) = @_; my $gotop = $server->channel_find($target)->nick_find($server->{nick})->{op}; my $verbose = Irssi::settings_get_bool('badword_verbose') == 1; my $luser = $nick_dbase{$nick}{$target}{'count'}; if ($luser == Irssi::settings_get_int('badword_limit')) { $nick_dbase{$nick}{$target}{'count'} = 0; # Ban'em! my @host = split(/\@/, $address); if (($host[0] =~ /\~/) || ($host[0] =~ /\-/) || ($host[0] =~ /\=/) || ($host[0] =~ /\^/)) { $host[0] = "*"; } my $mask = '*!'.$host[0].'@'.$host[1]; $nick_dbase{$nick}{$target}{'kcount'} = ($nick_dbase{$nick}{$target}{'kcount'})+1; if ((Irssi::settings_get_int('badword_ban_delay') > 0) && (Irssi::settings_get_int('badword_ban_delay') == $nick_dbase{$nick}{$target}{'kcount'})) { if( $gotop ) { $server->command('mode '.$target.' +b '.$mask); } else { push(@{$action_queue{$target}},'mode '.$target.' +b '.$mask); } $nick_dbase{$nick}{$target}{'kcount'} = 0; Irssi::print('BW: Nick '.$nick.' with mask '.$mask.' punished for badwording on channel '.$target.' - banned') if($verbose); } else { Irssi::print('BW: Nick '.$nick.' with mask '.$mask.' punished for badwording on channel '.$target.' - kicked') if($verbose); } if( $gotop ) { $server->command('quote kick '.$target.' '.$nick.' :'.Irssi::settings_get_str('badword_reason')); } else { push(@{$action_queue{$target}},'quote kick '.$target.' '.$nick.' :'.Irssi::settings_get_str('badword_reason')); } } elsif( $luser == 1 ) { if( Irssi::settings_get_str('badword_warning_1') ne '' ) { $server->command("msg $nick " . Irssi::settings_get_str('badword_warning_1') ); } } elsif( $luser == 2 ) { if( Irssi::settings_get_str('badword_warning_2') ne '' ) { $server->command("msg $nick " . Irssi::settings_get_str('badword_warning_2') ); } } elsif( $luser == 3 ) { if( Irssi::settings_get_str('badword_warning_3') ne '' ) { $server->command("msg $nick " . Irssi::settings_get_str('badword_warning_3') ); } } if(!$gotop and defined $action_queue{$target}) { Irssi::print('BW: Actions pending, trying to op myself in ' . $target) if($verbose); $server->command('quote privmsg chanserv :op ' . $target) unless($gotop); } } sub sig_nick { my ($server, $newnick, $nick, $address) = @_; $newnick = substr ($newnick, 1) if ($newnick =~ /^:/); my $count = $nick_dbase{$nick}; if ($count) { $nick_dbase{$nick} = undef; $nick_dbase{$newnick} = $count; if (Irssi::settings_get_bool('badword_verbose') == 1) { Irssi::print('BW: Tranferred badwords from '.$nick.' to '.$newnick); } } } sub sig_mode_change { my ($channel,$nick) = @_; my $verbose = Irssi::settings_get_bool('badword_verbose') == 1; return unless($channel->{ownnick}->{nick} eq $nick->{nick}); return unless($nick->{op}); return unless(defined $action_queue{$channel->{name}} ); Irssi::print('BW: Performing actions pending for ' . $channel->{name}) if($verbose); foreach my $action (@{$action_queue{$channel->{name}}}) { $channel->command($action); } $action_queue{$channel->{name}} = undef; $channel->command("deop $nick->{nick}"); } sub mask_match ($$) { my ($what, $match) = @_; # stolen from shasta's friend.pl $match =~ s/\\/\\\\/g; $match =~ s/\./\\\./g; $match =~ s/\*/\.\*/g; $match =~ s/\!/\\\!/g; $match =~ s/\?/\./g; $match =~ s/\+/\\\+/g; $match =~ s/\^/\\\^/g; $match =~ s/\[/\\\[/g; return ($what =~ /^$match$/i); } Irssi::settings_add_str("misc", "badword_channels", ""); Irssi::settings_add_str("misc", "badword_words", ""); Irssi::settings_add_str("misc", "badword_words_instantban", ""); Irssi::settings_add_str("misc", "badword_reason", "BW: badword limit exceeded"); Irssi::settings_add_int("misc", "badword_limit", 3); Irssi::settings_add_int("misc", "badword_clear_delay", 3600); Irssi::settings_add_int("misc", "badword_ban_delay", 1); Irssi::settings_add_bool("misc", "badword_verbose", 0); Irssi::settings_add_str("misc", "badword_exemptions", "ChanServ!ChanServ\@services."); Irssi::settings_add_str("misc", "badword_warning_1", ""); Irssi::settings_add_str("misc", "badword_warning_2", ""); Irssi::settings_add_str("misc", "badword_warning_3", ""); Irssi::signal_add_last('message public', 'sig_public'); Irssi::signal_add_last('event nick', 'sig_nick'); Irssi::signal_add_last('nick mode changed','sig_mode_change');