diff options
Diffstat (limited to 'scripts/ban.pl')
-rw-r--r-- | scripts/ban.pl | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/scripts/ban.pl b/scripts/ban.pl new file mode 100644 index 0000000..8915dbc --- /dev/null +++ b/scripts/ban.pl @@ -0,0 +1,394 @@ +use Irssi 20020300; +use 5.6.0; +use strict; +use Socket; +use POSIX; + +use vars qw($VERSION %IRSSI %HELP); +$HELP{ban} = " +BAN [channel] [-normal|-host|-user|-domain|-crap|-ip|-class -before \"command\"|-after \"command\" nicks|masks] ... + +Bans the specified nicks or userhost masks. + +If nick is given as parameter, the ban type is used to generate the ban mask. +/SET banpl_type specified the default ban type. Ban type is one of the following: + + normal - *!fahren\@*.ds14.agh.edu.pl + host - *!*\@plus.ds14.agh.edu.pl + user - *!fahren@* + domain - *!*\@*.agh.edu.pl + crap - *?fah???\@?l??.?s??.??h.???.?l + ip - *!fahren\@149.156.124.* + class - *!*\@149.156.124.* + +Only one flag can be specified for a given nick. +Script removes any conflicting bans before banning. + +You can specify command that will be executed before or after +banning nick/mask using -before or -after. + +Examples: + /BAN fahren - Bans the nick 'fahren' + /BAN -ip fahren - Bans the ip of nick 'fahren' + /BAN fahren -ip fantazja -crap nerhaf -normal ff + - Bans 'fahren' (using banpl_type set), ip of 'fantazja', + host with crap mask of 'nerhaf' and 'ff' with normal bantype. + /BAN *!*fahren@* - Bans '*!*fahren@*' + /BAN #chan -after \"KICK #chan fahren :reason\" fahren + - Bans and kicks 'fahren' from channel '#chan' with reason 'reason'. + + /ALIAS ipkb ban \$C -after \"KICK \$C \$0 \$1-\" -ip \$0 + - Adds command /ipkb <nick> [reason] which kicks 'nick' and bans it's ip address. +"; +$VERSION = "1.4d"; +%IRSSI = ( + authors => "Maciek \'fahren\' Freudenheim", + contact => "fahren\@bochnia.pl", + name => "ban", + description => "/BAN [channel] [-normal|-host|-user|-domain|-crap|-ip|-class -before|-after \"cmd\" nick|mask] ... - bans several nicks/masks on channel, removes any conflicting bans before banning", + license => "GNU GPLv2 or later", + changed => "Tue Nov 19 18:11:09 CET 2002" +); + +# Changelog: +# 1.4d +# - getting user@host of someone who isn't on channel was broken +# 1.4c +# - fixed banning of unresolved hosts +# - fixed problem with /ban unexisting_nick other_nick +# 1.4b +# - doesn't require op to see banlist :) +# 1.4 +# - few fixes +# - using banpl_type instead of irssi's builtin ban_type +# - changed -normal behaviour +# 1.3 +# - :( fixed crap banning (yes, i'm to stupid to code it) +# 1.2 +# - queuing MODES for nicks that aren't on channel +# 1.11 +# - fixed .. surprise! crap banning +# - added use 5.6.0 +# 1.1 +# - fixed banning 10-char long idents +# - fixed crap banning (once more) +# - added -before and -after [command] for executing command before/after setting ban +# 1.0 +# - -o+b if banning opped nick +# - fixed -crap banning +# - always banning with *!*ident@ (instead of *!ident@) +# - can take channel as first argument now +# - displays error if it couldn't resolve host for -ip / -class ban +# - groups all modes and sends them at once, ie. -bbo\n+b-o+b +# - gets user@host via USERHOST if requested ban of someone who is not on channel +# - added help + +my (%ftag, $parent, %modes, %modes_args, %b, @userhosts); + +sub cmd_ban { + my ($args, $server, $winit) = @_; + + my $chan; + my ($channel) = $args =~ /^([^\s]+)/; + + if (($server->ischannel($channel))) { + $args =~ s/^[^\s]+\s?//; + return unless ($args); + unless (($chan = $server->channel_find($channel)) && $chan->{chanop}) { + Irssi::print("%R>>%n You are not on $channel or you are not opped."); + Irssi::signal_stop(); + return; + } + } else { + return unless ($args); + unless ($winit && $winit->{type} eq "CHANNEL" && $winit->{chanop}) { + Irssi::print("%R>>%n You don't have active channel in that window or you are not opped."); + Irssi::signal_stop(); + return; + } + $chan = $winit; + $channel = $chan->{name}; + } + + Irssi::signal_stop(); + + my $bantype = Irssi::settings_get_str("banpl_type"); + my $max = $server->{max_modes_in_cmd}; + my ($cmdwhat, $cmdwhen) = (0, 0); + $b{$channel} = 0; + + # counts nicks/masks to ban, lame :| + for my $cmd (split("\"", $args)) { + ($cmdwhen) and $cmdwhen = 0, next; + for (split(/ +/, $cmd)) { + next unless $_; + /^-(normal|host|user|domain|crap|ip|class)$/ and next; + /^-(before|after)$/ and $cmdwhen = 1, next; + $b{$channel}++; + } + } + + for my $cmd (split("\"", $args)) { + ($cmdwhen && !$cmdwhat) and $cmdwhat = $cmd, next; + for my $arg (split(/ +/, $cmd)) { + next unless $arg; + $arg =~ /^-(normal|host|user|domain|crap|ip|class)$/ and $bantype = $1, next; + $arg eq "-before" and $cmdwhen = 1, next; + $arg eq "-after" and $cmdwhen = 2, next; + + if (index($arg, "@") == -1) { + my $n; + if ($n = $chan->nick_find($arg)) { + # nick is on channel + + my ($user, $host) = split("@", $n->{host}); + + if ($bantype eq "ip" || $bantype eq "class") { + # requested ip ban, forking + my $pid = &ban_fork; + unless (defined $pid) { # error + $cmdwhen = $cmdwhat = 0; + $b{$channel}--; + next; + } elsif ($pid) { # parent + $cmdwhen = $cmdwhat = 0; + next; + } + my $ia = gethostbyname($host); + unless ($ia) { + print($parent "error $channel %R>>%n Couldn't resolve $host.\n"); + } else { + print($parent "execute $server->{tag} $channel " . (($n->{op})? $arg : 0) . " " . make_ban($user, inet_ntoa($ia), $bantype) . " $cmdwhen $cmdwhat\n"); + } + close $parent; POSIX::_exit(1); + } + ban_execute($chan, (($n->{op})? $arg : 0), make_ban($user, $host, $bantype), $max, $cmdwhen, $cmdwhat); + } else { + # nick is not on channel, trying to get addres via /userhost + $server->redirect_event('userhost', 1, $arg, 0, undef, { + 'event 302' => 'redir ban userhost', + '' => 'event empty' } ); + $server->send_raw("USERHOST :$arg"); + my $uh = { + tag => $server->{tag}, + nick => lc($arg), + channel => $channel, + chanhash => $chan, + bantype => $bantype, + cmdwhen => $cmdwhen, + cmdwhat => $cmdwhat + }; + push @userhosts, $uh; + } + } else { + # specified mask + my $ban; + $ban = "*!" if (index($arg, "!") == -1); + $ban .= $arg; + ban_execute($chan, 0, $ban, $max, $cmdwhen, $cmdwhat); + } + + $cmdwhen = $cmdwhat = 0; + } + } +} + +sub push_mode ($$$$) { + my ($chan, $mode, $arg, $max) = @_; + + my $channel = $chan->{name}; + $modes{$channel} .= $mode; + $modes_args{$channel} .= "$arg "; + + flush_mode($chan) if (length($modes{$channel}) >= ($max * 2)); +} + +sub flush_mode ($) { + my $chan = shift; + + my $channel = $chan->{name}; + return unless (defined $modes{$channel}); +# Irssi::print("MODE $channel $modes{$channel} $modes_args{$channel}"); + $chan->command("MODE $channel $modes{$channel} $modes_args{$channel}"); + undef $modes{$channel}; undef $modes_args{$channel}; +} + +sub userhost_red { + my ($server, $data) = @_; + $data =~ s/^[^ ]* :?//; + + my $uh = shift @userhosts; + + unless ($data && $data =~ /^([^=\*]*)\*?=.(.*)@(.*)/ && lc($1) eq $uh->{nick}) { + Irssi::print("%R>>%n No such nickname: $uh->{nick}"); + $b{$uh->{channel}}--; + flush_mode($uh->{chanhash}) unless ($b{$uh->{channel}}); + return; + } + + my ($user, $host) = (lc($2), lc($3)); + + if ($uh->{bantype} eq "ip" || $uh->{bantype} eq "class") { + # requested ip ;/ + my $pid = &ban_fork; + unless (defined $pid) { # error + $b{$uh->{channel}}--; + return; + } elsif ($pid) { # parent + return; + } + my $ia = gethostbyname($host); + unless ($ia) { + print($parent "error " . $uh->{channel} . " %R>>%n Couldn't resolve $host.\n"); + } else { + print($parent "execute " . $uh->{tag} . " " . $uh->{channel} . " 0 " . make_ban($user, inet_ntoa($ia), $uh->{bantype}) . " " . $uh->{cmdwhen} . " " . $uh->{cmdwhat} . "\n"); + } + close $parent; POSIX::_exit(1); + } + + my $serv = Irssi::server_find_tag($uh->{tag}); + ban_execute($uh->{chanhash}, 0, make_ban($user, $host, $uh->{bantype}), $serv->{max_modes_in_cmd}, $uh->{cmdwhen}, $uh->{cmdwhat}); +} + +sub ban_execute ($$$$$$) { + my ($chan, $nick, $ban, $max, $cmdwhen, $cmdwhat) = @_; + + my $no = 0; + my $channel = $chan->{name}; + + for my $hash ($chan->bans()) { + if (mask_match($ban, $hash->{ban})) { + # should display also who set the ban (if available) + Irssi::print("%Y>>%n $channel: ban $hash->{ban}"); + $no = 1; + last; + } elsif (mask_match($hash->{ban}, $ban)) { + push_mode($chan, "-b", $hash->{ban}, $max); + } + } + + unless ($no) { + my ($cmdmode, $cmdarg); + # is requested command a MODE so we can put it to queue? + ($cmdmode, $cmdarg) = $cmdwhat =~ /^MODE\s+[^\s]+\s+([^\s]+)\s+([^\s]+)/i if $cmdwhen; + if ($cmdwhen == 1) { # command requested *before* banning + unless ($cmdmode) { # command isn't mode, ie: KICK + flush_mode($chan); # flush all -b conflicting bans + $chan->command($cmdwhat); # execute + } else { # command is MODE, we can add it to queue + push_mode($chan, $cmdmode, $cmdarg, $max); + } + } + push_mode($chan, "-o", $nick, $max) if ($nick); + push_mode($chan, "+b", $ban, $max); + if ($cmdwhen == 2) { # command requested *after* banning + unless ($cmdmode) { + flush_mode($chan); # flush all modes + $chan->command($cmdwhat); + } else { + push_mode($chan, $cmdmode, $cmdarg, $max); + } + } + } + + $b{$channel}--; + flush_mode($chan) unless ($b{$channel}); +} + +sub ban_fork { + my ($rh, $wh); + pipe($rh, $wh); + my $pid = fork(); + unless (defined $pid) { + Irssi::print("%R>>%n Failed to fork() :/ - $!"); + close $rh; close $wh; + return undef; + } elsif ($pid) { # parent + close $wh; + $ftag{$rh} = Irssi::input_add(fileno($rh), INPUT_READ, \&ifork, $rh); + Irssi::pidwait_add($pid); + } else { # child + close $rh; + $parent = $wh; + } + return $pid; +} + +sub ifork { + my $rh = shift; + while (<$rh>) { + /^error\s([^\s]+)\s(.+)/ and $b{$1}--, Irssi::print("$2"), last; + if (/^execute\s([^\s]+)\s([^\s]+)\s([^\s]+)\s([^\s]+)\s([^\s]+)\s(.+)/) { + my $serv = Irssi::server_find_tag($1); + ban_execute($serv->channel_find($2), $3, $4, $serv->{max_modes_in_cmd}, $5, $6); + last; + } + } + Irssi::input_remove($ftag{$rh}); + delete $ftag{$rh}; + close $rh; +} + +sub make_ban ($$$) { + my ($user, $host, $bantype) = @_; + + $user =~ s/^[~+\-=^]/*/; + if ($bantype eq "ip") { + $host =~ s/\.[0-9]+$/.*/; + } elsif ($bantype eq "class") { + $user = "*"; + $host =~ s/\.[0-9]+$/.*/; + } elsif ($bantype eq "user") { + $host = "*"; + } elsif ($bantype eq "domain") { + # i know -- lame + if ($host =~ /^.*\..*\..*\..*$/) { + $host =~ s/.*(\..+\..+\..+)$/*\1/; + } elsif ($host =~ /^.*\..*\..*$/) { + $host =~ s/.*(\..+\..+)$/*\1/; + } + $user = "*"; + } elsif ($bantype eq "host") { + $user = "*"; + } elsif ($bantype eq "normal") { +# $host =~ s/^[A-Za-z\-]*[0-9]+\./*./; + if ($host =~ /\d$/) { + $host =~ s/\.[0-9]+$/.*/; + } else { + $host =~ s/^[^.]+\./*./ if $host =~ /^.*\..*\..*$/; + } + } elsif ($bantype eq "crap") { + my $crap; + for my $c (split(//, $user)) { + $crap .= ((int(rand(2)))? "?" : $c); + } + $user = $crap; + $crap = ""; + for my $c (split(//, $host)) { + $crap .= ((int(rand(2)))? "?" : $c); + } + $host = $crap; + } + + return ("*!" . $user . "@" . $host); +} + +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::command_bind 'ban' => \&cmd_ban; +Irssi::settings_add_str 'misc', 'banpl_type', 'normal'; +Irssi::signal_add 'redir ban userhost' => \&userhost_red; |