/var/www/www.irssi.org-old/scripts/html/ban.pl


   1 use Irssi 20020300;
   2 use 5.6.0;
   3 use strict;
   4 use Socket;
   5 use POSIX;
   6 
   7 use vars qw($VERSION %IRSSI %HELP);
   8 $HELP{ban} = "
   9 BAN [channel] [-normal|-host|-user|-domain|-crap|-ip|-class -before \"command\"|-after \"command\" nicks|masks] ...
  10 
  11 Bans the specified nicks or userhost masks.
  12 
  13 If 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 
  24 Only one flag can be specified for a given nick.
  25 Script removes any conflicting bans before banning.
  26 
  27 You can specify command that will be executed before or after
  28 banning nick/mask using -before or -after.
  29 
  30 Examples:
  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 
  86 my (%ftag, $parent, %modes, %modes_args, %b, @userhosts);
  87 
  88 sub 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 
 196 sub 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 
 206 sub 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 
 216 sub 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 
 253 sub 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 
 298 sub 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 
 317 sub 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 						
 332 sub 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 
 376 sub 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 
 392 Irssi::command_bind 'ban' => \&cmd_ban;
 393 Irssi::settings_add_str 'misc', 'banpl_type', 'normal';
 394 Irssi::signal_add 'redir ban userhost' => \&userhost_red;