Fixed bug in setbg
[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 # Current IPset. This is the name of an IP match set (See 
75 # https://kirkkosinski.com/2013/11/mass-blocking-evil-ip-addresses-iptables-ip-sets/)
76 # Each set can hold up to 65535 entries. We are currently on set 2.
77 #
78 # TODO: This code should handle the case where the set fills and we need to go
79 #       to the next set. Something like "ipset list <current set> | wc - " and
80 #       if it's > than say 60000, start a new set.
81 #
82 #       Also, when a new set comes around we need to do:
83 #         $ iptables -A FORWARD -m set --mach-set <newset> src -j DROP
84 my $currIPSet = 'BICE2';
85
86 my $update    = 1;
87 my $email     = 1;
88 my $hostname  = `hostname`;
89 chomp $hostname;
90
91 if ($hostname =~ /(\w*)\./) {
92   $hostname = $1;
93 } # if
94
95 sub AddToIPSet($) {
96   my ($ip) = @_;
97
98   my ($status, @output) = Execute "/sbin/ipset add $currIPSet $ip 2>&1";
99
100   if ($status) {
101     return if $output[0] =~ /already added/;
102
103     error "Unable to add $ip to ipset $currIPSet" . join ("\n", @output), 1;
104   } else {
105     return;
106   } # if
107 } # AddToIPSet
108
109 # Use whois(1) to get the email addresses of the responsible parties for an IP
110 # address. Note that a hash is used to eliminate duplicates.
111 sub GetEmailAddresses ($) {
112   my ($ip) = @_;
113
114   # List of whois servers to try
115   # Apparently whois.opensrs.net no longer offers whois service?
116   my @whois_list = (
117     '',
118     'whois.arin.net',
119     'whois.nsiregistry.net',
120     #'whois.opensrs.net',
121     'whois.networksolutions.com',
122   );
123
124   my %email_addresses;
125
126   for (@whois_list) {
127     my @lines;
128
129     if ($_ eq "") {
130       @lines = grep { /.*\@.*/ } `whois $ip`;
131     } else {
132       @lines = grep {/.*\@.*/ } `whois -h $_ $ip`;
133     } # if
134
135     for (@lines) {
136       my @fields = split /:/, $_;
137
138       $_ = $fields [@fields - 1];
139
140       if (/(\S+\@\S[\.\S]+)/) {
141         $email_addresses{$1} = "";
142       } # if
143     } # for
144
145     # Break out of loop if we found email addresses
146     last unless keys %email_addresses;
147   } # for
148
149   return keys %email_addresses;
150 } # GetEmailAddresses
151
152 # Send email to the responsible parties.
153 sub SendEmail ($$$$$$) {
154   my ($to, $subject, $message, $ip, $attempts, $violationNbr) = @_;
155
156   if ($email) {
157     verbose "$violationNbr: Reporting $ip ($attempts violations) to $to";
158   } else {
159     verbose "$violationNbr: Would have reported $ip ($attempts violations) to $to";
160     return;
161   } # if
162
163   mail (
164     from    => "BICE\@$domain",
165     to      => $to,
166     #cc      => $contact,
167     subject => $subject,
168     mode    => 'html',
169     data    => $message,
170   );
171 } # SendEmail
172
173 sub processLogfile () {
174   my %violations;
175
176   # Note: Normally you must be root to open up $security_logfile
177   open my $readlog, '<', $security_logfile
178     or error "Unable to open $security_logfile - $!", 1;
179
180   flock $readlog, LOCK_EX
181     or error "Unable to flock $security_logfile", 1;
182
183   my @lines;
184
185   while (<$readlog>) {
186     my $newline = $_;
187
188     if (/^(\S+\s+\S+\s+\S+)\s+.*Invalid user (\w+) from (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) {
189       my %violation = $violations{$3} ? %{$violations{$3}} : %_;
190
191       push @{$violation{$2}}, $1;
192
193       $violations{$3} = \%violation;
194
195       $newline =~ s/Invalid user/INVALID USER/;
196     } elsif (/^(\S+\s+\S+\s+\S+)\s+.*authentication failure.*ruser=(\S+).*rhost=(\S+)/) {
197       my %violation = $violations{$3} ? %{$violations{$3}} : %_;
198
199       push @{$violation{$2}}, $1;
200
201       $violations{$3} = \%violation;
202
203       $newline =~ s/authentication failure/AUTHENTICATION FAILURE/;
204     } elsif (/^(\S+\s+\S+\s+\S+)\s+.*Failed password for (\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/Failed password/FAILED PASSWORD/;
212     } # if
213
214     push @lines, $newline; 
215   } # while
216
217   return %violations unless $update;
218
219   flock $readlog, LOCK_UN
220     or error "Unable to unlock $security_logfile", 1;
221
222   close $readlog;
223
224   open my $writelog, '>', $security_logfile
225     or error "Unable to open $security_logfile for writing - $!", 1;
226
227   flock $writelog, LOCK_EX
228     or error "Unable to flock $security_logfile", 1;
229
230   print $writelog $_ for @lines;
231
232   flock $writelog, LOCK_UN
233     or error "Unable to unlock $security_logfile", 1;
234
235   close $writelog;
236
237   return %violations;
238 } # processLogfile
239
240 # Report breakins to the authorities.
241 sub ReportBreakins () {
242   my %violations = processLogfile;
243
244   my $nbrViolations = keys %violations;
245
246   if ($nbrViolations == 0) {
247     verbose 'No violations found';
248   } elsif ($nbrViolations == 1) {
249     verbose '1 site attempting to violate our perimeter';
250   } else {
251     verbose "$nbrViolations sites attempting to violate our perimeter";
252   } # if
253
254   my $violations;
255
256   for my $ip (sort keys %violations) {
257     my $attempts;
258
259     $violations++;
260     $attempts += @{$violations{$ip}{$_}} for (keys %{$violations{$ip}});
261
262     my @emails = GetEmailAddresses $ip;
263
264     unless (@emails) {
265       verbose 'Unable to find any responsible parties for detected breakin '
266             . "attempts from IP $ip ($attempts breakin attempts)";
267       next;
268     } # unless
269
270     my $to      = join ',', @emails;
271     my $subject = "Illegal attempts to break into $domain from your domain";
272     my $message = <<"END";
273 <p>Somebody from your domain with an IP Address of <b>$ip</b> has been
274 attempting to break into my domain, <b>$domain</b>. <u>Breaking into somebody
275 else's computer is illegal and criminal prosecution can result!</u> As a
276 responsible ISP it is in your best interests to investigate such activity and to
277 shutdown any such illegal activity as it is a violation of law and most likely a
278 violation of your user level agreement. It is expected that you will investigate
279 this and send the result and/or disposition of your investigation back to
280 $contact. <font color=red><b>If you fail to do so then criminal prosecution may
281 result!</b></font></p>
282
283 <p>Please be aware that <b>none</b> of these attempts to breakin have been
284 successful - this system is configured such that only trusted users are allowed
285 to log in as they must provide authenticated keys in advance. So your attempts
286 have been wholly unsuccessful. Still, this does not diminish the illegality nor
287 the ability of us to pursue this matter in a court of law.</p>
288
289 <p>There were a total of $attempts attempts to break into $domain. The following
290 is a report of the breakin attempts from IP Address $ip along with the usernames
291 attempted and the time of the attempt:</p>
292
293 <p>Note: $domain is located in $location. All times are $UTC:</p>
294
295 <ol>
296 END
297     # Report users
298     for my $user (sort keys %{$violations{$ip}}) {
299       if (@{$violations{$ip}{$user}} == 1) {
300         $message .= "<li>The user <b>$user</b> attempted access on $violations{$ip}{$user}[0]</li>";
301       } else {
302         $message .= "<li>The user <b>$user</b> attemped access on the following date/times:</li>"; 
303         $message .= "<ol>";
304         $message .= "<li>$_</li>" for (@{$violations{$ip}{$user}});
305         $message .= "</ol>";
306       } # if
307     } # for
308
309     $message .= '</ol><p>Your prompt attention to this matter is expected '
310               . 'and will be appreciated.</p>';
311     SendEmail $to, $subject, $message, $ip, $attempts, $violations;
312     AddToIPSet $ip;
313   } # for
314
315   return;
316 } # ReportBreakins
317
318 ## Main
319 GetOptions (
320   'verbose', sub { set_verbose },
321   'debug',   sub { set_debug },
322   'usage',   sub { Usage },
323   'update!', \$update,
324   'mail!',   \$email,
325   'file=s',  \$security_logfile,
326 ) || Usage;
327
328 Usage 'Must specify filename' unless $security_logfile;
329
330 ReportBreakins;
331
332 =pod
333
334 =head1 CONFIGURATION AND ENVIRONMENT
335
336 DEBUG: If set then $debug is set to this level.
337
338 VERBOSE: If set then $verbose is set to this level.
339
340 TRACE: If set then $trace is set to this level.
341
342 =head1 DEPENDENCIES
343
344 =head2 Perl Modules
345
346 L<FindBin>
347
348 L<Getopt::Long|Getopt::Long>
349
350 L<Fcntl>
351
352 =head2 ClearSCM Perl Modules
353
354 =begin man 
355
356  Display
357  Mail
358  Utils
359
360 =end man
361
362 =begin html
363
364 <blockquote>
365 <<a href="http://clearscm.com/php/scm_man.php?file=lib/Display.pm">Display</a><br>
366 <a href="http://clearscm.com/php/scm_man.php?file=lib/Mail.pm">Mail</a><br>
367 a href="http://clearscm.com/php/scm_man.php?file=lib/Utils.pm">Utils</a><br>
368 </blockquote>
369
370 =end html
371
372 =head1 BUGS AND LIMITATIONS
373
374 There are no known bugs in this script
375
376 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
377
378 =head1 LICENSE AND COPYRIGHT
379
380 Copyright (c) 2010, ClearSCM, Inc. All rights reserved.
381
382 =cut