1 # ho_hammer.pl
  2 #
  3 # $Id: ho_hammer.pl,v 1.5 2004/09/11 12:21:49 jvunder REL_0_3 $
  4 #
  5 # Part of the Hybrid Oper Script Collection.
  6 #
  7 # Looks for hammering clients and acts upon them.
  8 #
  9 # TODO: code HOSC::Kliner and use it.
 10 
 11 use strict;
 12 use vars qw($VERSION %IRSSI $SCRIPT_NAME);
 13 
 14 use Irssi;
 15 use Irssi::Irc;
 16 use HOSC::again;
 17 use HOSC::again 'HOSC::Base';
 18 use HOSC::again 'HOSC::Tools';
 19 use HOSC::again 'HOSC::Kliner';
 20 import HOSC::Tools qw{is_server_notice};
 21 
 22 # ---------------------------------------------------------------------
 23 
 24 ($VERSION) = '$Revision: 1.5 $' =~ / (\d+\.\d+) /;
 25 %IRSSI = (
 26     authors => 'Garion',
 27     contact => 'garion@efnet.nl',
 28     name    => 'ho_hammer',
 29     description => 'Looks for hammering clients and acts upon them.',
 30     license => 'Public Domain',
 31     url     => 'http://www.garion.org/irssi/hosc.php',
 32     changed => '19 January 2003 13:07:07',
 33 );
 34 $SCRIPT_NAME = 'Hammer';
 35 
 36 # Hashtable with connection times per host
 37 # Key is the host
 38 # Value is an array of connection times (unix timestamp)
 39 my %conntimes;
 40 
 41 # The last time the connection hash has been cleaned (unix timestamp)
 42 my $conntimes_last_cleaned = 0;
 43 
 44 my $kliner = HOSC::Kliner->new();
 45 
 46 # ---------------------------------------------------------------------
 47 # A Server Event has occurred. Check if it is a server NOTICE; 
 48 # if so, process it.
 49 
 50 sub event_serverevent {
 51     my ($server, $msg, $nick, $hostmask) = @_;
 52 
 53     return unless is_server_notice(@_);
 54   
 55     process_event($server, $msg);
 56 }
 57 
 58 
 59 # ---------------------------------------------------------------------
 60 # This function takes a server notice and matches it with a few regular
 61 # expressions to see if any special action needs to be taken.
 62 
 63 sub process_event {
 64     my ($server, $msg) = @_;
 65 
 66     # HYBRID 7 - need a setting to determine which server type we have!
 67     # Client connect: nick, user, host, ip, class, realname
 68     if ($msg =~ /Client connecting: (.*) \((.*)@(.*)\) \[(.*)\] {(.*)} \[(.*)\]/) {
 69         process_connect($server, $1, $2, $3, $4, $5, $6);
 70         return;
 71     }
 72 
 73     # HYBRID 6
 74     # Client connect: nick, user, host, ip, class, realname
 75     if ($msg =~ /Client connecting: (.*) \((.*)@(.*)\) \[(.*)\] {(.*)}/) {
 76         process_connect($server, $1, $2, $3, $4, $5, undef);
 77         return;
 78     }
 79 }
 80 
 81 # ---------------------------------------------------------------------
 82 # This function processes a client connect.
 83 # It shows warning in case of:
 84 # - many connects in a short timespan from a single host
 85 
 86 sub process_connect {
 87     my ($server, $nick, $user, $host, $ip, $class, $realname) = @_;
 88 
 89     return unless Irssi::settings_get_bool('ho_hammer_enable');
 90 
 91     return if $ip eq '255.255.255.255' &&
 92         Irssi::settings_get_bool('ho_hammer_ignore_spoofs');
 93 
 94     my $tag = $server->{tag};
 95 
 96     # Check whether the server notice is on one of the networks we are
 97     # monitoring.
 98     my $watch_this_network = 0;
 99     foreach my $network (split /\s+/, 
100         lc(Irssi::settings_get_str('ho_hammer_network_tags'))
101     ) {
102         if ($network eq lc($server->{tag})) {
103             $watch_this_network = 1;
104             last;
105         }
106     }
107     return unless $watch_this_network;
108 
109     my $now = time();
110     push @{ $conntimes{$tag}->{$host} }, $now;
111 
112     # Check whether this host has connected more than
113     # ho_hammer_warning_count times in the past
114     # ho_hammer_warning_time seconds.
115     if (@{ $conntimes{$tag}->{$host} } == 
116         Irssi::settings_get_int('ho_hammer_warning_count')
117     ) {
118         # Get the time of the first connect
119         my $firsttime = ${ $conntimes{$tag}->{$host} }[0];
120 
121         # Get the time of the last connect
122         my $lasttime = ${ $conntimes{$tag}->{$host} }[@{ $conntimes{$tag}->{$host} } - 1];
123 
124         my $timediff = $lasttime - $firsttime;
125 
126         if ($timediff < Irssi::settings_get_int('ho_hammer_warning_time')) {
127             ho_print_warning("Hammer: " . @{ $conntimes{$tag}->{$host} } . "/".
128                 "$timediff: $nick ($user\@$host).");
129         }
130     }
131 
132     # Check whether this host has connected more than
133     # ho_hammer_violation_count times in the past
134     # ho_hammer_violation_time seconds.
135     if (@{ $conntimes{$tag}->{$host} } >= 
136         Irssi::settings_get_int('ho_hammer_violation_count')) {
137         # Get the time of the first connect
138         my $firsttime = ${ $conntimes{$tag}->{$host} }[0];
139 
140         # Get the time of the last connect
141         my $lasttime = ${ $conntimes{$tag}->{$host} }[@{ $conntimes{$tag}->{$host} } - 1];
142 
143         my $timediff = $lasttime - $firsttime;
144 
145         if ($timediff < Irssi::settings_get_int('ho_hammer_violation_time')) {
146             my $time   = Irssi::settings_get_int('ho_hammer_kline_time');
147             my $reason = Irssi::settings_get_str('ho_hammer_kline_reason');
148 
149             # If number of connections is equal to max number of connections
150             # allowed, kline user@host. If it is higher, that means the user
151             # has been k-lined once and has changed ident; therefore, kline
152             # *@host.
153             if (@{ $conntimes{$tag}->{$host} } ==
154                 Irssi::settings_get_int('ho_hammer_violation_count')
155             ) {
156                 $kliner->kline(
157                     server => $server, 
158                     user   => $user,
159                     host   => $host,
160                     reason => $reason,
161                 );
162                 ho_print("K-lined $user\@$host for hammering.");
163             } else {
164                 $kliner->kline(
165                     server => $server, 
166                     user   => '*',
167                     host   => $host,
168                     reason => $reason,
169                 );
170                 ho_print("K-lined *\@$host for hammering.");
171             }
172         }
173     }
174 
175     # Clean up the connection times hash to make sure it doesn't grow
176     # to infinity :)
177     # Do this every 60 seconds.
178     if ($now > $conntimes_last_cleaned + 60) {
179         $conntimes_last_cleaned = $now;
180         cleanup_conntimes_hash(300);
181     }
182 }
183 
184 
185 
186 # ---------------------------------------------------------------------
187 # Cleans up the connection times hash.
188 # The only argument is the number of seconds to keep the hostnames for.
189 # This means that if the last connection from a hostname was longer ago
190 # than that number of seconds, the hostname is dropped from the hash.
191 
192 sub cleanup_conntimes_hash {
193     my ($keeptime) = @_;
194     my $now = time();
195   
196     # If the last time this host has connected is over $keeptime secs ago,
197     # delete it.
198     for my $tag (keys %conntimes) {
199         for my $host (keys %{ $conntimes{$tag} }) {
200             my $lasttime = ${ $conntimes{$tag}->{$host} }[@{ $conntimes{$tag}->{$host} } - 1];
201 
202             # Discard this host if no connections have been made from it during
203             # the last $keeptime seconds.
204             if ($now > $lasttime + $keeptime) {
205                 delete $conntimes{$tag}->{$host};
206             }
207         }
208     }
209 }
210 
211 # ---------------------------------------------------------------------
212 # The /hammer command.
213 
214 sub cmd_hammer {
215     my ($data, $server, $item) = @_;
216     if ($data =~ m/^[(help)]/i ) {
217         Irssi::command_runsub ('hammer', $data, $server, $item);
218     } else {
219         ho_print("Use /HAMMER HELP for help.")
220     }
221 }
222 
223 # ---------------------------------------------------------------------
224 # The /hammer help command.
225 
226 sub cmd_hammer_help {
227     print_help();
228 }
229 
230 # ---------------------------------------------------------------------
231 
232 ho_print_init_begin();
233 
234 Irssi::signal_add_first('server event', 'event_serverevent');
235 
236 Irssi::command_bind('hammer',      'cmd_hammer');
237 Irssi::command_bind('hammer help', 'cmd_hammer_help');
238 
239 Irssi::settings_add_bool('ho', 'ho_hammer_enable',            0);
240 Irssi::settings_add_bool('ho', 'ho_hammer_ignore_spoofs',     1);
241 Irssi::settings_add_int('ho', 'ho_hammer_warning_count',      8);
242 Irssi::settings_add_int('ho', 'ho_hammer_warning_time',     100);
243 Irssi::settings_add_int('ho', 'ho_hammer_violation_count',   10);
244 Irssi::settings_add_int('ho', 'ho_hammer_violation_time',   120);
245 Irssi::settings_add_int('ho', 'ho_hammer_kline_time',      1440);
246 Irssi::settings_add_str('ho', 'ho_hammer_network_tags',      '');
247 Irssi::settings_add_str('ho', 'ho_hammer_kline_reason', 
248     '[Automated K-line] Reconnecting too fast. Please try again later.');
249 
250 if (length Irssi::settings_get_str('ho_hammer_network_tags') > 0) {
251     if (Irssi::settings_get_bool('ho_hammer_enable')) {
252         ho_print("Script enabled for the following tags: " .
253             Irssi::settings_get_str('ho_hammer_network_tags'));
254     } else {
255         ho_print("Script disabled. The following tags have been set: " .
256             Irssi::settings_get_str('ho_hammer_network_tags') . 
257             ". Use /SET ho_hammer_enable ON to enable the script.");
258     }
259 } else {
260     ho_print_warning("No network tags set. Please use ".
261         "/SET ho_hammer_network_tags tag1 tag2 tag3 .. ".
262         "to choose the tags the script will work on.");
263 }
264 
265 ho_print_init_end();
266 ho_print("Use /HAMMER HELP for help.");
267 
268 # ---------------------------------------------------------------------
269 
270 sub print_help {
271     ho_print_help('head', $SCRIPT_NAME);
272 
273     ho_print_help('section', 'Description');
274     ho_print_help("This script tracks reconnecting clients and can take action on ".
275         "them, being either printing a warning or banning them from the server ".
276         "automatically. Clients that reconnect rapidly are called 'hammering ".
277         "clients', which explains the name of this script.\n");
278 
279     ho_print_help('section', 'Settings');
280     ho_print_help('setting', 'ho_hammer_enable', 
281         'Master setting to enable/disable this script.');
282     ho_print_help('setting', 'ho_hammer_network_tags', 
283         'Tags of the networks hammering clients must be tracked.');
284     ho_print_help('setting', 'ho_hammer_ignore_spoofs', 
285         'Whether spoofs should be ignored.');
286     ho_print_help('setting', 'ho_hammer_kline_reason', 
287         'The reason of the ban placed on the hammering client.');
288 
289     ho_print_help('setting', 'ho_hammer_warning_count', 'and');
290     ho_print_help('setting', 'ho_hammer_warning_time', 
291         'If clients from a host connect more than ho_hammer_warning_count '.
292         'times in ho_hammer_warning_time seconds, a warning is printed.');
293 
294     ho_print_help('setting', 'ho_hammer_violation_count', 'and');
295     ho_print_help('setting', 'ho_hammer_violation_time', 
296         'If clients from a host connect more than ho_hammer_violation_count '.
297         'times in ho_violation_warning_time seconds, the host is banned.');
298 }


syntax highlighted by Code2HTML, v. 0.9.1