1use Irssi 20020300; 2use 5.6.0; 3use strict; 4use Socket; 5use POSIX; 6 7use vars qw($VERSION %IRSSI %HELP); 8$HELP{ban} = " 9BAN [channel] [-normal|-host|-user|-domain|-crap|-ip|-class -before \"command\"|-after \"command\" nicks|masks] ... 10 11Bans the specified nicks or userhost masks. 12 13If nick is given as parameter, the ban type is used to generate the ban mask. 14/SET banpl_type specified the default ban type. Ban type is one of the following: 15 16 normal - *!fahren\@*.ds14.agh.edu.pl 17 host - *!*\@plus.ds14.agh.edu.pl 18 user - *!fahren@* 19 domain - *!*\@*.agh.edu.pl 20 crap - *?fah???\@?l??.?s??.??h.???.?l 21 ip - *!fahren\@149.156.124.* 22 class - *!*\@149.156.124.* 23 24Only one flag can be specified for a given nick. 25Script removes any conflicting bans before banning. 26 27You can specify command that will be executed before or after 28banning nick/mask using -before or -after. 29 30Examples: 31 /BAN fahren - Bans the nick 'fahren' 32 /BAN -ip fahren - Bans the ip of nick 'fahren' 33 /BAN fahren -ip fantazja -crap nerhaf -normal ff 34 - Bans 'fahren' (using banpl_type set), ip of 'fantazja', 35 host with crap mask of 'nerhaf' and 'ff' with normal bantype. 36 /BAN *!*fahren@* - Bans '*!*fahren@*' 37 /BAN #chan -after \"KICK #chan fahren :reason\" fahren 38 - Bans and kicks 'fahren' from channel '#chan' with reason 'reason'. 39 40 /ALIAS ipkb ban \$C -after \"KICK \$C \$0 \$1-\" -ip \$0 41 - Adds command /ipkb <nick> [reason] which kicks 'nick' and bans it's ip address. 42"; 43$VERSION = "1.4d"; 44%IRSSI = ( 45 authors => "Maciek \'fahren\' Freudenheim", 46 contact => "fahren\@bochnia.pl", 47 name => "ban", 48 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", 49 license => "GNU GPLv2 or later", 50 changed => "Tue Nov 19 18:11:09 CET 2002" 51); 52 53# Changelog: 54# 1.4d 55# - getting user@host of someone who isn't on channel was broken 56# 1.4c 57# - fixed banning of unresolved hosts 58# - fixed problem with /ban unexisting_nick other_nick 59# 1.4b 60# - doesn't require op to see banlist :) 61# 1.4 62# - few fixes 63# - using banpl_type instead of irssi's builtin ban_type 64# - changed -normal behaviour 65# 1.3 66# - :( fixed crap banning (yes, i'm to stupid to code it) 67# 1.2 68# - queuing MODES for nicks that aren't on channel 69# 1.11 70# - fixed .. surprise! crap banning 71# - added use 5.6.0 72# 1.1 73# - fixed banning 10-char long idents 74# - fixed crap banning (once more) 75# - added -before and -after [command] for executing command before/after setting ban 76# 1.0 77# - -o+b if banning opped nick 78# - fixed -crap banning 79# - always banning with *!*ident@ (instead of *!ident@) 80# - can take channel as first argument now 81# - displays error if it couldn't resolve host for -ip / -class ban 82# - groups all modes and sends them at once, ie. -bbo\n+b-o+b 83# - gets user@host via USERHOST if requested ban of someone who is not on channel 84# - added help 85 86my (%ftag, $parent, %modes, %modes_args, %b, @userhosts); 87 88sub cmd_ban { 89 my ($args, $server, $winit) = @_; 90 91 my $chan; 92 my ($channel) = $args =~ /^([^\s]+)/; 93 94 if (($server->ischannel($channel))) { 95 $args =~ s/^[^\s]+\s?//; 96 return unless ($args); 97 unless (($chan = $server->channel_find($channel)) && $chan->{chanop}) { 98 Irssi::print("%R>>%n You are not on $channel or you are not opped."); 99 Irssi::signal_stop(); 100 return; 101 } 102 } else { 103 return unless ($args); 104 unless ($winit && $winit->{type} eq "CHANNEL" && $winit->{chanop}) { 105 Irssi::print("%R>>%n You don't have active channel in that window or you are not opped."); 106 Irssi::signal_stop(); 107 return; 108 } 109 $chan = $winit; 110 $channel = $chan->{name}; 111 } 112 113 Irssi::signal_stop(); 114 115 my $bantype = Irssi::settings_get_str("banpl_type"); 116 my $max = $server->{max_modes_in_cmd}; 117 my ($cmdwhat, $cmdwhen) = (0, 0); 118 $b{$channel} = 0; 119 120 # counts nicks/masks to ban, lame :| 121 for my $cmd (split("\"", $args)) { 122 ($cmdwhen) and $cmdwhen = 0, next; 123 for (split(/ +/, $cmd)) { 124 next unless $_; 125 /^-(normal|host|user|domain|crap|ip|class)$/ and next; 126 /^-(before|after)$/ and $cmdwhen = 1, next; 127 $b{$channel}++; 128 } 129 } 130 131 for my $cmd (split("\"", $args)) { 132 ($cmdwhen && !$cmdwhat) and $cmdwhat = $cmd, next; 133 for my $arg (split(/ +/, $cmd)) { 134 next unless $arg; 135 $arg =~ /^-(normal|host|user|domain|crap|ip|class)$/ and $bantype = $1, next; 136 $arg eq "-before" and $cmdwhen = 1, next; 137 $arg eq "-after" and $cmdwhen = 2, next; 138 139 if (index($arg, "@") == -1) { 140 my $n; 141 if ($n = $chan->nick_find($arg)) { 142 # nick is on channel 143 144 my ($user, $host) = split("@", $n->{host}); 145 146 if ($bantype eq "ip" || $bantype eq "class") { 147 # requested ip ban, forking 148 my $pid = &ban_fork; 149 unless (defined $pid) { # error 150 $cmdwhen = $cmdwhat = 0; 151 $b{$channel}--; 152 next; 153 } elsif ($pid) { # parent 154 $cmdwhen = $cmdwhat = 0; 155 next; 156 } 157 my $ia = gethostbyname($host); 158 unless ($ia) { 159 print($parent "error $channel %R>>%n Couldn't resolve $host.\n"); 160 } else { 161 print($parent "execute $server->{tag} $channel " . (($n->{op})? $arg : 0) . " " . make_ban($user, inet_ntoa($ia), $bantype) . " $cmdwhen $cmdwhat\n"); 162 } 163 close $parent; POSIX::_exit(1); 164 } 165 ban_execute($chan, (($n->{op})? $arg : 0), make_ban($user, $host, $bantype), $max, $cmdwhen, $cmdwhat); 166 } else { 167 # nick is not on channel, trying to get addres via /userhost 168 $server->redirect_event('userhost', 1, $arg, 0, undef, { 169 'event 302' => 'redir ban userhost', 170 '' => 'event empty' } ); 171 $server->send_raw("USERHOST :$arg"); 172 my $uh = { 173 tag => $server->{tag}, 174 nick => lc($arg), 175 channel => $channel, 176 chanhash => $chan, 177 bantype => $bantype, 178 cmdwhen => $cmdwhen, 179 cmdwhat => $cmdwhat 180 }; 181 push @userhosts, $uh; 182 } 183 } else { 184 # specified mask 185 my $ban; 186 $ban = "*!" if (index($arg, "!") == -1); 187 $ban .= $arg; 188 ban_execute($chan, 0, $ban, $max, $cmdwhen, $cmdwhat); 189 } 190 191 $cmdwhen = $cmdwhat = 0; 192 } 193 } 194} 195 196sub push_mode ($$$$) { 197 my ($chan, $mode, $arg, $max) = @_; 198 199 my $channel = $chan->{name}; 200 $modes{$channel} .= $mode; 201 $modes_args{$channel} .= "$arg "; 202 203 flush_mode($chan) if (length($modes{$channel}) >= ($max * 2)); 204} 205 206sub flush_mode ($) { 207 my $chan = shift; 208 209 my $channel = $chan->{name}; 210 return unless (defined $modes{$channel}); 211# Irssi::print("MODE $channel $modes{$channel} $modes_args{$channel}"); 212 $chan->command("MODE $channel $modes{$channel} $modes_args{$channel}"); 213 undef $modes{$channel}; undef $modes_args{$channel}; 214} 215 216sub userhost_red { 217 my ($server, $data) = @_; 218 $data =~ s/^[^ ]* :?//; 219 220 my $uh = shift @userhosts; 221 222 unless ($data && $data =~ /^([^=\*]*)\*?=.(.*)@(.*)/ && lc($1) eq $uh->{nick}) { 223 Irssi::print("%R>>%n No such nickname: $uh->{nick}"); 224 $b{$uh->{channel}}--; 225 flush_mode($uh->{chanhash}) unless ($b{$uh->{channel}}); 226 return; 227 } 228 229 my ($user, $host) = (lc($2), lc($3)); 230 231 if ($uh->{bantype} eq "ip" || $uh->{bantype} eq "class") { 232 # requested ip ;/ 233 my $pid = &ban_fork; 234 unless (defined $pid) { # error 235 $b{$uh->{channel}}--; 236 return; 237 } elsif ($pid) { # parent 238 return; 239 } 240 my $ia = gethostbyname($host); 241 unless ($ia) { 242 print($parent "error " . $uh->{channel} . " %R>>%n Couldn't resolve $host.\n"); 243 } else { 244 print($parent "execute " . $uh->{tag} . " " . $uh->{channel} . " 0 " . make_ban($user, inet_ntoa($ia), $uh->{bantype}) . " " . $uh->{cmdwhen} . " " . $uh->{cmdwhat} . "\n"); 245 } 246 close $parent; POSIX::_exit(1); 247 } 248 249 my $serv = Irssi::server_find_tag($uh->{tag}); 250 ban_execute($uh->{chanhash}, 0, make_ban($user, $host, $uh->{bantype}), $serv->{max_modes_in_cmd}, $uh->{cmdwhen}, $uh->{cmdwhat}); 251} 252 253sub ban_execute ($$$$$$) { 254 my ($chan, $nick, $ban, $max, $cmdwhen, $cmdwhat) = @_; 255 256 my $no = 0; 257 my $channel = $chan->{name}; 258 259 for my $hash ($chan->bans()) { 260 if (mask_match($ban, $hash->{ban})) { 261 # should display also who set the ban (if available) 262 Irssi::print("%Y>>%n $channel: ban $hash->{ban}"); 263 $no = 1; 264 last; 265 } elsif (mask_match($hash->{ban}, $ban)) { 266 push_mode($chan, "-b", $hash->{ban}, $max); 267 } 268 } 269 270 unless ($no) { 271 my ($cmdmode, $cmdarg); 272 # is requested command a MODE so we can put it to queue? 273 ($cmdmode, $cmdarg) = $cmdwhat =~ /^MODE\s+[^\s]+\s+([^\s]+)\s+([^\s]+)/i if $cmdwhen; 274 if ($cmdwhen == 1) { # command requested *before* banning 275 unless ($cmdmode) { # command isn't mode, ie: KICK 276 flush_mode($chan); # flush all -b conflicting bans 277 $chan->command($cmdwhat); # execute 278 } else { # command is MODE, we can add it to queue 279 push_mode($chan, $cmdmode, $cmdarg, $max); 280 } 281 } 282 push_mode($chan, "-o", $nick, $max) if ($nick); 283 push_mode($chan, "+b", $ban, $max); 284 if ($cmdwhen == 2) { # command requested *after* banning 285 unless ($cmdmode) { 286 flush_mode($chan); # flush all modes 287 $chan->command($cmdwhat); 288 } else { 289 push_mode($chan, $cmdmode, $cmdarg, $max); 290 } 291 } 292 } 293 294 $b{$channel}--; 295 flush_mode($chan) unless ($b{$channel}); 296} 297 298sub ban_fork { 299 my ($rh, $wh); 300 pipe($rh, $wh); 301 my $pid = fork(); 302 unless (defined $pid) { 303 Irssi::print("%R>>%n Failed to fork() :/ - $!"); 304 close $rh; close $wh; 305 return undef; 306 } elsif ($pid) { # parent 307 close $wh; 308 $ftag{$rh} = Irssi::input_add(fileno($rh), INPUT_READ, \&ifork, $rh); 309 Irssi::pidwait_add($pid); 310 } else { # child 311 close $rh; 312 $parent = $wh; 313 } 314 return $pid; 315} 316 317sub ifork { 318 my $rh = shift; 319 while (<$rh>) { 320 /^error\s([^\s]+)\s(.+)/ and $b{$1}--, Irssi::print("$2"), last; 321 if (/^execute\s([^\s]+)\s([^\s]+)\s([^\s]+)\s([^\s]+)\s([^\s]+)\s(.+)/) { 322 my $serv = Irssi::server_find_tag($1); 323 ban_execute($serv->channel_find($2), $3, $4, $serv->{max_modes_in_cmd}, $5, $6); 324 last; 325 } 326 } 327 Irssi::input_remove($ftag{$rh}); 328 delete $ftag{$rh}; 329 close $rh; 330} 331 332sub make_ban ($$$) { 333 my ($user, $host, $bantype) = @_; 334 335 $user =~ s/^[~+\-=^]/*/; 336 if ($bantype eq "ip") { 337 $host =~ s/\.[0-9]+$/.*/; 338 } elsif ($bantype eq "class") { 339 $user = "*"; 340 $host =~ s/\.[0-9]+$/.*/; 341 } elsif ($bantype eq "user") { 342 $host = "*"; 343 } elsif ($bantype eq "domain") { 344 # i know -- lame 345 if ($host =~ /^.*\..*\..*\..*$/) { 346 $host =~ s/.*(\..+\..+\..+)$/*\1/; 347 } elsif ($host =~ /^.*\..*\..*$/) { 348 $host =~ s/.*(\..+\..+)$/*\1/; 349 } 350 $user = "*"; 351 } elsif ($bantype eq "host") { 352 $user = "*"; 353 } elsif ($bantype eq "normal") { 354# $host =~ s/^[A-Za-z\-]*[0-9]+\./*./; 355 if ($host =~ /\d$/) { 356 $host =~ s/\.[0-9]+$/.*/; 357 } else { 358 $host =~ s/^[^.]+\./*./ if $host =~ /^.*\..*\..*$/; 359 } 360 } elsif ($bantype eq "crap") { 361 my $crap; 362 for my $c (split(//, $user)) { 363 $crap .= ((int(rand(2)))? "?" : $c); 364 } 365 $user = $crap; 366 $crap = ""; 367 for my $c (split(//, $host)) { 368 $crap .= ((int(rand(2)))? "?" : $c); 369 } 370 $host = $crap; 371 } 372 373 return ("*!" . $user . "@" . $host); 374} 375 376sub mask_match ($$) { 377 my ($what, $match) = @_; 378 379 # stolen from shasta's friend.pl 380 $match =~ s/\\/\\\\/g; 381 $match =~ s/\./\\\./g; 382 $match =~ s/\*/\.\*/g; 383 $match =~ s/\!/\\\!/g; 384 $match =~ s/\?/\./g; 385 $match =~ s/\+/\\\+/g; 386 $match =~ s/\^/\\\^/g; 387 $match =~ s/\[/\\\[/g; 388 389 return ($what =~ /^$match$/i); 390} 391 392Irssi::command_bind 'ban' => \&cmd_ban; 393Irssi::settings_add_str 'misc', 'banpl_type', 'normal'; 394Irssi::signal_add 'redir ban userhost' => \&userhost_red; 395