/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;