491a3047434f7d75d9c665d1e3c563851afd1145
[clearscm.git] / bin / bice.pl
1 #!/usr/bin/perl
2
3 =pod
4
5 =head1 NAME $RCSfile: bice.pl,v $
6
7 Report breakin attempts to this domain
8
9 =head1 VERSION
10
11 =over
12
13 =item Author
14
15 Andrew DeFaria <Andrew@ClearSCM.com>
16
17 =item Revision
18
19 $Revision: 1.3 $
20
21 =item Created:
22
23 Fri Mar 18 01:14:38 PST 2005
24
25 =item Modified:
26
27 $Date: 2013/05/30 15:35:27 $
28
29 =back
30
31 =head1 SYNOPSIS
32
33  Usage: bice [-u|sage] [-v|erbose] [-d|ebug] [-nou|pdate] [-nom|ail]
34              [-f|ilename <filename> ]
35
36  Where:
37    -u|sage     Print this usage
38    -v|erbose:  Verbose mode (Default: -verbose)
39    -nou|pdate: Don't update security logfile file (Default: -update)
40    -nom|ail:   Don't send emails (Default: -mail)
41    -f|ilename: Open alternate messages file (Default: /var/log/auth.log)
42
43 =head1 DESCRIPTION
44
45 This script will look at the security logfile for attempted breakins and then 
46 use whois to report them to the upstream provider.
47
48 =cut
49
50 use strict;
51 use warnings;
52
53 use FindBin;
54 use Getopt::Long;
55
56 use lib "$FindBin::Bin/../lib";
57
58 use Display;
59 use Mail;
60 use Utils;
61
62 use Fcntl ':flock'; # import LOCK_* constants
63
64 my $security_logfile = '/var/log/auth.log';
65
66 # Customize these variables
67 my $domain   = 'DeFaria.com';
68 my $contact  = 'Andrew@DeFaria.com';
69 my $location = 'Santa Clara, California, USA';
70 my $UTC      = 'UTC-8';
71 my $mailhost = $domain;
72 # End customize these variables
73
74 my $verbose;
75 my $update    = 1;
76 my $email     = 1;
77 my $hostname  = `hostname`;
78 chomp $hostname;
79
80 if ($hostname =~ /(\w*)\./) {
81   $hostname = $1;
82 } # if
83
84 sub AddToIPTables (@) {
85   my (@ips) = @_;
86
87   # We shouldn't need to weed out duplicate but ya never know  
88   my $ipfilename = '/etc/ipblock';
89
90   my $result = open my $ipfile, '<', $ipfilename;
91
92   my (%ips, @oldips);
93   
94   if ($result) {
95     @oldips = <$ipfile>; 
96   
97     close $ipfile if $ipfile;
98
99     chomp @oldips;
100   } # if
101
102   map { $ips{$_} = 1 } @oldips;
103   map { $ips{$_} = 1 } <@ips>;
104   
105   open $ipfile, '>', "$ipfilename"
106     or error "Unable to open $ipfilename - $!", 1;
107   
108   foreach (sort keys %ips) {
109     print $ipfile "$_\n";
110   } # foreach
111   
112   close $ipfile;
113
114   # Recreate the BICE chain
115   `/sbin/iptables -F BICE`;
116   `/sbin/iptables -X BICE`;
117   `/sbin/iptables -N BICE`;
118   
119   # Add all new @ips to iptables
120   `/sbin/iptables -A BICE -s $_ -p tcp -j DROP` foreach (sort keys %ips);
121   
122   return;
123 } # AddToIPTables
124
125 # Use whois(1) to get the email addresses of the responsible parties for an IP
126 # address. Note that a hash is used to eliminate duplicates.
127 sub GetEmailAddresses ($) {
128   my ($ip) = @_;
129
130   # List of whois servers to try
131   my @whois_list = (
132     '',
133     'whois.arin.net',
134     'whois.nsiregistry.net',
135     'whois.opensrs.net',
136     'whois.networksolutions.com',
137   );
138
139   my %email_addresses;
140
141   foreach (@whois_list) {
142     my @lines;
143
144     if ($_ eq "") {
145       @lines = grep { /.*\@.*/ } `whois $ip`;
146     } else {
147       @lines = grep {/.*\@.*/ } `whois -h $_ $ip`;
148     } # if
149
150     foreach (@lines) {
151       my @fields = split /:/, $_;
152       
153       $_ = $fields [@fields - 1];
154       
155       if (/(\S+\@\S[\.\S]+)/) {
156         $email_addresses{$1} = "";
157       } # if
158     } # foreach
159
160     # Break out of loop if we found email addresses
161     last unless keys %email_addresses;
162   } # foreach
163
164   return keys %email_addresses;
165 } # GetEmailAddresses
166
167 # Send email to the responsible parties.
168 sub SendEmail ($$$$$) {
169   my ($to, $subject, $message, $ip, $violations) = @_;
170
171   if ($email) {
172     verbose "Reporting $ip ($violations violations) to $to";
173   } else {
174     verbose "Would have reported $ip ($violations violations) to $to";
175     return;
176   } # if
177
178   mail (
179     from        => "BICE\@$domain",
180     to          => $to,
181     cc          => $contact,
182     subject     => $subject,
183     mode        => 'html',
184     data        => $message,
185   );
186 } # SendEmail
187
188 sub processLogfile () {
189   my %violations;
190   
191   # Note: Normally you must be root to open up $security_logfile
192   open my $readlog, '<', $security_logfile
193     or error "Unable to open $security_logfile - $!", 1;
194     
195   flock $readlog, LOCK_EX
196     or error "Unable to flock $security_logfile", 1;
197   
198   my @lines;
199   
200   while (<$readlog>) {
201     my $newline = $_;
202     
203     if (/^(\S+\s+\S+\s+\S+)\s+.*Invalid user (\w+) from (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) {
204       my %violation = $violations{$3} ? %{$violations{$3}} : %_;
205       
206       push @{$violation{$2}}, $1;
207
208       $violations{$3} = \%violation;
209       
210       $newline =~ s/Invalid user/INVALID USER/;
211     } elsif (/^(\S+\s+\S+\s+\S+)\s+.*authentication failure.*ruser=(\S+).*rhost=(\S+)/) {
212       my %violation = $violations{$3} ? %{$violations{$3}} : %_;
213       
214       push @{$violation{$2}}, $1;
215
216       $violations{$3} = \%violation;
217       
218       $newline =~ s/authentication failure/AUTHENTICATION FAILURE/;
219     } elsif (/^(\S+\s+\S+\s+\S+)\s+.*Failed password for (\w+) from (\d{1,3}\.\d{1,3}\.d{1,3}\.d{1,3})/) {
220       my %violation = $violations{$3} ? %{$violations{$3}} : %_;
221       
222       push @{$violation{$2}}, $1;
223
224       $violations{$3} = \%violation;
225       
226       $newline =~ s/Failed password/FAILED PASSWORD/;
227     } # if
228
229     push @lines, $newline; 
230   } # while
231   
232   return %violations unless $update;
233   
234   flock $readlog, LOCK_UN
235     or error "Unable to unlock $security_logfile", 1;
236     
237   close $readlog;
238   
239   open my $writelog, '>', $security_logfile
240     or error "Unable to open $security_logfile for writing - $!", 1;
241   
242   flock $writelog, LOCK_EX
243     or error "Unable to flock $security_logfile", 1;
244     
245   print $writelog $_ foreach @lines;
246   
247   flock $writelog, LOCK_UN
248     or error "Unable to unlock $security_logfile", 1;
249
250   close $writelog;
251   
252   return %violations;
253 } # processLogfile
254
255 # Report breakins to the authorities.
256 sub ReportBreakins () {
257   my %violations = processLogfile;
258
259   my $nbrViolations = keys %violations;
260   
261   if ($nbrViolations == 0) {
262     verbose 'No violations found';
263   } elsif ($nbrViolations == 1) {
264     verbose '1 site attempting to violate our perimeter';
265   } else {
266     verbose "$nbrViolations sites attempting to violate our perimeter";
267   } # if
268   
269   foreach (sort keys %violations) {
270     my $ip = $_;
271
272     my $attempts;
273     
274     $attempts += @{$violations{$ip}{$_}} foreach (keys %{$violations{$ip}});
275     
276     my @emails   = GetEmailAddresses $ip;
277
278     unless (@emails) {
279       verbose 'Unable to find any responsible parties for detected breakin '
280             . "attempts from IP $ip ($attempts breakin attempts)";
281       next;
282     } # unless
283     
284     my $to      = join ',', @emails;
285     my $subject = "Illegal attempts to break into $domain from your domain";
286     my $message = <<"END";
287 <p>Somebody from your domain with an IP Address of <b>$ip</b> has been
288 attempting to break into my domain, <b>$domain</b>. <u>Breaking into somebody
289 else's computer is illegal and criminal prosecution can result!</u> As a
290 responsible ISP it is in your best interests to investigate such activity and to
291 shutdown any such illegal activity as it is a violation of law and most likely a
292 violation of your user level agreement. It is expected that you will investigate
293 this and send the result and/or disposition of your investigation back to
294 $contact. <font color=red><b>If you fail to do so then criminal prosecution may
295 result!</b></font></p>
296
297 <p>Please be aware that <b>none</b> of these attempts to breakin have been
298 successful - this system is configured such that only trusted users are allowed
299 to log in as they must provide authenticated keys in advance. So your attempts
300 have been wholly unsuccessful. Still, this does not diminish the illegality nor
301 the ability of us to pursue this matter in a court of law.</p>
302
303 <p>There were a total of $attempts attempts to break into $domain. The following
304 is a report of the breakin attempts from IP Address $ip along with the usernames
305 attempted and the time of the attempt:</p>
306
307 <p>Note: $domain is located in $location. All times are $UTC:</p>
308
309 <ol>
310 END
311     # Report users
312     foreach my $user (sort keys %{$violations{$ip}}) {
313       if (@{$violations{$ip}{$user}} == 1) {
314           $message .= "<li>The user <b>$user</b> attempted access on $violations{$ip}{$user}[0]</li>";
315       } else {
316         $message .= "<li>The user <b>$user</b> attemped access on the following date/times:</li>"; 
317         $message .= "<ol>";
318         $message .= "<li>$_</li>" foreach (@{$violations{$ip}{$user}});
319         $message .= "</ol>";
320         } # if
321     } # foreach
322
323     $message .= '</ol><p>Your prompt attention to this matter is expected '
324               . 'and will be appreciated.</p>';
325     SendEmail $to, $subject, $message, $ip, $attempts;
326   } # foreach
327   
328   AddToIPTables keys %violations;
329 } # ReportBreakins
330
331 ## Main
332
333 # Get options
334 GetOptions (
335   'verbose', sub { set_verbose },
336   'debug',   sub { set_debug },
337   'usage',   sub { Usage },
338   'update!', \$update,
339   'mail!',   \$email,
340   'file=s',  \$security_logfile,
341 ) || Usage;
342
343 Usage 'Must specify filename'
344   unless $security_logfile;
345
346 ReportBreakins;
347
348 =pod
349
350 =head1 CONFIGURATION AND ENVIRONMENT
351
352 DEBUG: If set then $debug is set to this level.
353
354 VERBOSE: If set then $verbose is set to this level.
355
356 TRACE: If set then $trace is set to this level.
357
358 =head1 DEPENDENCIES
359
360 =head2 Perl Modules
361
362 L<FindBin>
363
364 L<Getopt::Long|Getopt::Long>
365
366 L<Fcntl>
367
368 =head2 ClearSCM Perl Modules
369
370 =begin man 
371
372  Display
373  Mail
374  Utils
375
376 =end man
377
378 =begin html
379
380 <blockquote>
381 <<a href="http://clearscm.com/php/scm_man.php?file=lib/Display.pm">Display</a><br>
382 <a href="http://clearscm.com/php/scm_man.php?file=lib/Mail.pm">Mail</a><br>
383 a href="http://clearscm.com/php/scm_man.php?file=lib/Utils.pm">Utils</a><br>
384 </blockquote>
385
386 =end html
387
388 =head1 BUGS AND LIMITATIONS
389
390 There are no known bugs in this script
391
392 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
393
394 =head1 LICENSE AND COPYRIGHT
395
396 Copyright (c) 2010, ClearSCM, Inc. All rights reserved.
397
398 =cut