Fixed announceEmail.pl to be less busy
[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 = 'Phoenix, Arizona, USA';
70 my $UTC      = 'UTC-7';
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   # Apparently whois.opensrs.net no longer offers whois service?
132   my @whois_list = (
133     '',
134     'whois.arin.net',
135     'whois.nsiregistry.net',
136     #'whois.opensrs.net',
137     'whois.networksolutions.com',
138   );
139
140   my %email_addresses;
141
142   foreach (@whois_list) {
143     my @lines;
144
145     if ($_ eq "") {
146       @lines = grep { /.*\@.*/ } `whois $ip`;
147     } else {
148       @lines = grep {/.*\@.*/ } `whois -h $_ $ip`;
149     } # if
150
151     foreach (@lines) {
152       my @fields = split /:/, $_;
153
154       $_ = $fields [@fields - 1];
155
156       if (/(\S+\@\S[\.\S]+)/) {
157         $email_addresses{$1} = "";
158       } # if
159     } # foreach
160
161     # Break out of loop if we found email addresses
162     last unless keys %email_addresses;
163   } # foreach
164
165   return keys %email_addresses;
166 } # GetEmailAddresses
167
168 # Send email to the responsible parties.
169 sub SendEmail ($$$$$) {
170   my ($to, $subject, $message, $ip, $violations) = @_;
171
172   if ($email) {
173     verbose "Reporting $ip ($violations violations) to $to";
174   } else {
175     verbose "Would have reported $ip ($violations violations) to $to";
176     return;
177   } # if
178
179   mail (
180     from    => "BICE\@$domain",
181     to      => $to,
182     #cc      => $contact,
183     subject => $subject,
184     mode    => 'html',
185     data    => $message,
186   );
187 } # SendEmail
188
189 sub processLogfile () {
190   my %violations;
191
192   # Note: Normally you must be root to open up $security_logfile
193   open my $readlog, '<', $security_logfile
194     or error "Unable to open $security_logfile - $!", 1;
195
196   flock $readlog, LOCK_EX
197     or error "Unable to flock $security_logfile", 1;
198
199   my @lines;
200
201   while (<$readlog>) {
202     my $newline = $_;
203
204     if (/^(\S+\s+\S+\s+\S+)\s+.*Invalid user (\w+) from (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) {
205       my %violation = $violations{$3} ? %{$violations{$3}} : %_;
206
207       push @{$violation{$2}}, $1;
208
209       $violations{$3} = \%violation;
210
211       $newline =~ s/Invalid user/INVALID USER/;
212     } elsif (/^(\S+\s+\S+\s+\S+)\s+.*authentication failure.*ruser=(\S+).*rhost=(\S+)/) {
213       my %violation = $violations{$3} ? %{$violations{$3}} : %_;
214
215       push @{$violation{$2}}, $1;
216
217       $violations{$3} = \%violation;
218
219       $newline =~ s/authentication failure/AUTHENTICATION FAILURE/;
220     } elsif (/^(\S+\s+\S+\s+\S+)\s+.*Failed password for (\w+) from (\d{1,3}\.\d{1,3}\.d{1,3}\.d{1,3})/) {
221       my %violation = $violations{$3} ? %{$violations{$3}} : %_;
222
223       push @{$violation{$2}}, $1;
224
225       $violations{$3} = \%violation;
226
227       $newline =~ s/Failed password/FAILED PASSWORD/;
228     } # if
229
230     push @lines, $newline; 
231   } # while
232
233   return %violations unless $update;
234
235   flock $readlog, LOCK_UN
236     or error "Unable to unlock $security_logfile", 1;
237
238   close $readlog;
239
240   open my $writelog, '>', $security_logfile
241     or error "Unable to open $security_logfile for writing - $!", 1;
242
243   flock $writelog, LOCK_EX
244     or error "Unable to flock $security_logfile", 1;
245
246   print $writelog $_ foreach @lines;
247
248   flock $writelog, LOCK_UN
249     or error "Unable to unlock $security_logfile", 1;
250
251   close $writelog;
252
253   return %violations;
254 } # processLogfile
255
256 # Report breakins to the authorities.
257 sub ReportBreakins () {
258   my %violations = processLogfile;
259
260   my $nbrViolations = keys %violations;
261
262   if ($nbrViolations == 0) {
263     verbose 'No violations found';
264   } elsif ($nbrViolations == 1) {
265     verbose '1 site attempting to violate our perimeter';
266   } else {
267     verbose "$nbrViolations sites attempting to violate our perimeter";
268   } # if
269
270   foreach (sort keys %violations) {
271     my $ip = $_;
272
273     my $attempts;
274
275     $attempts += @{$violations{$ip}{$_}} foreach (keys %{$violations{$ip}});
276
277     my @emails   = GetEmailAddresses $ip;
278
279     unless (@emails) {
280       verbose 'Unable to find any responsible parties for detected breakin '
281             . "attempts from IP $ip ($attempts breakin attempts)";
282       next;
283     } # unless
284
285     my $to      = join ',', @emails;
286     my $subject = "Illegal attempts to break into $domain from your domain";
287     my $message = <<"END";
288 <p>Somebody from your domain with an IP Address of <b>$ip</b> has been
289 attempting to break into my domain, <b>$domain</b>. <u>Breaking into somebody
290 else's computer is illegal and criminal prosecution can result!</u> As a
291 responsible ISP it is in your best interests to investigate such activity and to
292 shutdown any such illegal activity as it is a violation of law and most likely a
293 violation of your user level agreement. It is expected that you will investigate
294 this and send the result and/or disposition of your investigation back to
295 $contact. <font color=red><b>If you fail to do so then criminal prosecution may
296 result!</b></font></p>
297
298 <p>Please be aware that <b>none</b> of these attempts to breakin have been
299 successful - this system is configured such that only trusted users are allowed
300 to log in as they must provide authenticated keys in advance. So your attempts
301 have been wholly unsuccessful. Still, this does not diminish the illegality nor
302 the ability of us to pursue this matter in a court of law.</p>
303
304 <p>There were a total of $attempts attempts to break into $domain. The following
305 is a report of the breakin attempts from IP Address $ip along with the usernames
306 attempted and the time of the attempt:</p>
307
308 <p>Note: $domain is located in $location. All times are $UTC:</p>
309
310 <ol>
311 END
312     # Report users
313     foreach my $user (sort keys %{$violations{$ip}}) {
314       if (@{$violations{$ip}{$user}} == 1) {
315         $message .= "<li>The user <b>$user</b> attempted access on $violations{$ip}{$user}[0]</li>";
316       } else {
317         $message .= "<li>The user <b>$user</b> attemped access on the following date/times:</li>"; 
318         $message .= "<ol>";
319         $message .= "<li>$_</li>" foreach (@{$violations{$ip}{$user}});
320         $message .= "</ol>";
321       } # if
322     } # foreach
323
324     $message .= '</ol><p>Your prompt attention to this matter is expected '
325               . 'and will be appreciated.</p>';
326     SendEmail $to, $subject, $message, $ip, $attempts;
327   } # foreach
328
329   AddToIPTables keys %violations;
330 } # ReportBreakins
331
332 ## Main
333
334 # Get options
335 GetOptions (
336   'verbose', sub { set_verbose },
337   'debug',   sub { set_debug },
338   'usage',   sub { Usage },
339   'update!', \$update,
340   'mail!',   \$email,
341   'file=s',  \$security_logfile,
342 ) || Usage;
343
344 Usage 'Must specify filename'
345   unless $security_logfile;
346
347 ReportBreakins;
348
349 =pod
350
351 =head1 CONFIGURATION AND ENVIRONMENT
352
353 DEBUG: If set then $debug is set to this level.
354
355 VERBOSE: If set then $verbose is set to this level.
356
357 TRACE: If set then $trace is set to this level.
358
359 =head1 DEPENDENCIES
360
361 =head2 Perl Modules
362
363 L<FindBin>
364
365 L<Getopt::Long|Getopt::Long>
366
367 L<Fcntl>
368
369 =head2 ClearSCM Perl Modules
370
371 =begin man 
372
373  Display
374  Mail
375  Utils
376
377 =end man
378
379 =begin html
380
381 <blockquote>
382 <<a href="http://clearscm.com/php/scm_man.php?file=lib/Display.pm">Display</a><br>
383 <a href="http://clearscm.com/php/scm_man.php?file=lib/Mail.pm">Mail</a><br>
384 a href="http://clearscm.com/php/scm_man.php?file=lib/Utils.pm">Utils</a><br>
385 </blockquote>
386
387 =end html
388
389 =head1 BUGS AND LIMITATIONS
390
391 There are no known bugs in this script
392
393 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
394
395 =head1 LICENSE AND COPYRIGHT
396
397 Copyright (c) 2010, ClearSCM, Inc. All rights reserved.
398
399 =cut