]> git.ipfire.org Git - ipfire-2.x.git/blame - config/guardian/guardian.pl
Merge branch 'master' of git://git.ipfire.org/ipfire-2.x
[ipfire-2.x.git] / config / guardian / guardian.pl
CommitLineData
7c4cc0d8
CS
1#!/usr/bin/perl
2# V 1.7
3# Read the readme file for changes
4#
fb30e20c
CS
5# Enhanced for IPFire by IPFire Team
6# Added Portscan detection for non syslog system
7# Added SSH-Watch for SSH-Bruteforce Attacks
8# An suppected IP will be blocked on all interfaces
7c4cc0d8
CS
9
10$OS=`uname`;
11chomp $OS;
12print "OS shows $OS\n";
13
14require 'getopts.pl';
15
16&Getopts ('hc:d');
17if (defined($opt_h)) {
18 print "Guardian v1.7 \n";
19 print "guardian.pl [-hd] <-c config>\n";
20 print " -h shows help\n";
21 print " -d run in debug mode (doesn't fork, output goes to STDOUT)\n";
22 print " -c specifiy a configuration file other than the default (/etc/guardian.conf)\n";
23 exit;
24}
25&load_conf;
26&sig_handler_setup;
27
28print "My ip address and interface are: $hostipaddr $interface\n";
29
30if ($hostipaddr !~ /\d+\.\d+\.\d+\.\d+/) {
31 print "This ip address is bad : $hostipaddr\n";
32 die "I need a good host ipaddress\n";
33}
34
35$networkaddr = $hostipaddr;
36$networkaddr =~ s/\d+$/0/;
37$gatewayaddr = $hostipaddr;
38$gatewayaddr =~ s/\d+$/$hostgatewaybyte/;
39$broadcastaddr = $hostipaddr;
40$broadcastaddr =~ s/\d+$/255/;
41&build_ignore_hash;
42
43# This is the target hash. If a packet was destened to any of these, then the
44# sender of that packet will get denied, unless it is on the ignore list..
45
46%targethash = ( "$networkaddr" => 1,
47 "$broadcastaddr" => 1,
48 "0" => 1, # This is what gets sent to &checkem if no
49 # destination was found.
50 "$hostipaddr" => 1);
51
fb30e20c
CS
52%sshhash = ();
53
7c4cc0d8
CS
54if ( -e $targetfile ) {
55 &load_targetfile;
56}
57
58if (!defined($opt_d)) {
59 print "Becoming a daemon..\n";
60 &daemonize;
61} else { print "Running in debug mode..\n"; }
62
63open (ALERT, $alert_file) or die "can't open alert file: $alert_file: $!\n";
64seek (ALERT, 0, 2); # set the position to EOF.
65# this is the same as a tail -f :)
66$counter=0;
fb30e20c
CS
67open (ALERT2, "/var/log/messages" ) or die "can't open /var/log/messages: $!\n";
68seek (ALERT2, 0, 2); # set the position to EOF.
69# this is the same as a tail -f :)
fb30e20c 70
7c4cc0d8
CS
71for (;;) {
72 sleep 1;
73 if (seek(ALERT,0,1)){
74 while (<ALERT>) {
75 chop;
7c4cc0d8
CS
76 if (defined($opt_d)) {print "$_\n";}
77 if (/\[\*\*\]\s+(.*)\s+\[\*\*\]/){
78 $type=$1;
79 }
80 if (/(\d+\.\d+\.\d+\.\d+):\d+ -\> (\d+\.\d+\.\d+\.\d+):\d+/) {
81 &checkem ($1, $2, $type);
82 }
fb30e20c
CS
83 if ($_=~/Portscan/) {
84 my @array=split(/ /,$_);&checkem ($array[5], $hostipaddr, "Portscan was detected.");}
7c4cc0d8
CS
85 }
86 }
f0ef6853 87
fb30e20c
CS
88 sleep 1;
89 if (seek(ALERT2,0,1)){
90 while (<ALERT2>) {
91 chop;
92 if ($_=~/.*sshd.*Failed password for root from.*/) {
93 my @array=split(/ /,$_);&checkssh ($array[10], "possible SSH-Bruteforce Attack");}
94 }
95 }
96 # Run this stuff every 30 seconds..
f0ef6853 97 if ($counter == 30) {
fb30e20c
CS
98 &remove_blocks; # This might get moved elsewhere, depending on how much load
99 # it puts on the system..
100 &check_log_name;
f0ef6853
CS
101 $counter=0;
102 } else { $counter=$counter+1; }
7c4cc0d8
CS
103}
104
105sub check_log_name {
106 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
107 $atime,$mtime,$ctime,$blksize,$blocks) = stat($alert_file);
108 if ($size < $previous_size) { # The filesize is smaller than last
109 close (ALERT); # we checked, so we need to reopen it
110 open (ALERT, "$alert_file"); # This should still work in our main while
111 $previous_size=$size; # loop (I hope)
112 write_log ("Log filename changed. Reopening $alert_file\n");
113 } else {
114 $previous_size=$size;
115 }
116}
117
118
119sub checkem {
120 my ($source, $dest,$type) = @_;
121 my $flag=0;
7c4cc0d8
CS
122 return 1 if ($source eq $hostipaddr); # this should prevent is from nuking
123 # ourselves
124 return 1 if ($source eq $gatewayaddr); # or our gateway
125 if ($ignore{$source} == 1) { # check our ignore list..
7c4cc0d8
CS
126 &write_log("$source\t$type\n");
127 &write_log("Ignoring attack because $source is in my ignore list\n");
128 return 1;
129 }
130 # if the offending packet was sent to us, the network, or the broadcast, then
131 if ($targethash{$dest} == 1) {
7c4cc0d8
CS
132 &ipchain ($source, $dest, $type);
133 }
134 # you will see this if the destination was not in the $targethash, and the
135 # packet was not ignored before the target check..
136 else {
137 &write_log ("Odd.. source = $source, dest = $dest - No action done.\n");
138 if (defined ($opt_d)) {
139 foreach $key (keys %targethash) {
140 &write_log ("targethash{$key} = $targethash{$key}\n");
141 }
142 }
143 }
144}
145
fb30e20c
CS
146sub checkssh {
147 my ($source,$type) = @_;
148 my $flag=0;
149 return 1 if ($source eq $hostipaddr); # this should prevent is from nuking
150 # ourselves
151 return 1 if ($source eq $gatewayaddr); # or our gateway
152 if ($sshhash{$dest} eq "" ){ $sshhash{$dest} = 1; }
153 if ($sshhash{$dest} >= 3 ) {
154 &write_log ("source = $source, count $sshhash{$dest} - blocking for ssh attack.\n");
155 &ipchain ($source, "", $type);
156 }
157 # you will see this if the destination was not in the $sshhash, and the
158 # packet was not ignored before the target check..
159 else {
160 &write_log ("Odd.. source = $source, ssh count only $sshhash{$dest} - No action done.\n");
161 if (defined ($opt_d)) {
162 foreach $key (keys %sshhash) {
163 &write_log ("sshhash{$key} = %sshhash{$key}\n");
164 }
165 }
166 $sshhash{$key} = $sshhash{$key}+1;
167 }
168}
169
7c4cc0d8
CS
170sub ipchain {
171 my ($source, $dest, $type) = @_;
172 &write_log ("$source\t$type\n");
173 if ($hash{$source} eq "") {
fb30e20c
CS
174 &write_log ("Running '$blockpath $source'\n");
175 system ("$blockpath $source");
7c4cc0d8
CS
176 $hash{$source} = time() + $TimeLimit;
177 } else {
178 # We have already blocked this one, but snort detected another attack. So
179 # we should update the time blocked..
180 $hash{$source} = time() + $TimeLimit;
181 }
182}
183
184sub build_ignore_hash {
185# This would cause is to ignore all broadcasts if it
186# got set.. However if unset, then the attacker could spoof the packet to make
187# it look like it came from the network, and a reply to the spoofed packet
188# could be seen if the attacker were on the local network.
189# $ignore{$networkaddr}=1;
190
191# same thing as above, just with the broadcast instead of the network.
192# $ignore{$broadcastaddr}=1;
193 my $count =0;
194 $ignore{$gatewayaddr}=1;
195 $ignore{$hostipaddr}=1;
196 if ($ignorefile ne "") {
197 open (IGNORE, $ignorefile);
198 while (<IGNORE>) {
199 chop;
200 next if (/\#/); #skip comments
201 next if (/^\s*$/); # and blank lines
202 $ignore{$_}=1;
203 $count++;
204 }
205 close (IGNORE);
206 print "Loaded $count addresses from $ignorefile\n";
207 } else {
208 print "No ignore file was loaded!\n";
209 }
210}
211
212sub load_conf {
213 if ($opt_c eq "") {
214 $opt_c = "/etc/guardian.conf";
215 }
216 if (! -e $opt_c) {
217 die "Need a configuration file.. please use to the -c option to name a
218configuration file\n";
219 }
220 open (CONF, $opt_c) or die "Cannot read the config file $opt_c, $!\n";
221 while (<CONF>) {
222 chop;
223 next if (/^\s*$/); #skip blank lines
224 next if (/^#/); # skip comment lines
225 if (/LogFile\s+(.*)/) {
226 $logfile = $1;
227 }
228 if (/Interface\s+(.*)/) {
229 $interface = $1;
230 }
231 if (/AlertFile\s+(.*)/) {
232 $alert_file = $1;
233 }
234 if (/IgnoreFile\s+(.*)/) {
235 $ignorefile = $1;
236 }
237 if (/TargetFile\s+(.*)/) {
238 $targetfile = $1;
239 }
240 if (/TimeLimit\s+(.*)/) {
241 $TimeLimit = $1;
242 }
243 if (/HostIpAddr\s+(.*)/) {
244 $hostipaddr = $1;
245 }
246 if (/HostGatewayByte\s+(.*)/) {
247 $hostgatewaybyte = $1;
248 }
249# if (/ipchainsPath\s+(.*)/) {
250# $ipchains_path = $1;
251# }
252 }
253 if ($interface eq "") {
254 die "Fatal! Interface is undefined.. Please define it in $opt_o with keyword Interface\n";
255 }
256 if ($alert_file eq "") {
257 print "Warning! AlertFile is undefined.. Assuming /var/log/snort.alert\n";
258 $alert_file="/var/log/snort.alert";
259 }
260 if ($hostipaddr eq "") {
261 print "Warning! HostIpAddr is undefined! Attempting to guess..\n";
262 $hostipaddr = &get_ip($interface);
263 print "Got it.. your HostIpAddr is $hostipaddr\n";
264 }
265 if ($ignorefile eq "") {
266 print "Warning! IgnoreFile is undefined.. going with default ignore list (hostname and gateway)!\n";
267 }
268 if ($hostgatewaybyte eq "") {
269 print "Warning! HostGatewayByte is undefined.. gateway will not be in ignore list!\n";
270 }
271 if ($logfile eq "") {
272 print "Warning! LogFile is undefined.. Assuming debug mode, output to STDOUT\n";
273 $opt_d = 1;
274 }
275 if (! -w $logfile) {
276 print "Warning! Logfile is not writeable! Engaging debug mode, output to STDOUT\n";
277 $opt_d = 1;
278 }
279 foreach $mypath (split (/:/, $ENV{PATH})) {
280 if (-x "$mypath/guardian_block.sh") {
281 $blockpath = "$mypath/guardian_block.sh";
282 }
283 if (-x "$mypath/guardian_unblock.sh") {
284 $unblockpath = "$mypath/guardian_unblock.sh";
285 }
286 }
287 if ($blockpath eq "") {
288 print "Error! Could not find guardian_block.sh. Please consult the README. \n";
289 exit;
290 }
291 if ($unblockpath eq "") {
292 print "Warning! Could not find guardian_unblock.sh. Guardian will not be\n";
293 print "able to remove blocked ip addresses. Please consult the README file\n";
294 }
295 if ($TimeLimit eq "") {
296 print "Warning! Time limit not defined. Defaulting to absurdly long time limit\n";
297 $TimeLimit = 999999999;
298 }
299}
300
7c4cc0d8
CS
301sub write_log {
302 my $message = $_[0];
fb30e20c 303 my $date = localtime();
7c4cc0d8
CS
304 if (defined($opt_d)) { # we are in debug mode, and not daemonized
305 print STDOUT $message;
306 } else {
307 open (LOG, ">>$logfile");
fb30e20c 308 print LOG $date.": ".$message;
7c4cc0d8
CS
309 close (LOG);
310 }
311}
312
7c4cc0d8
CS
313sub daemonize {
314 my ($home);
315 if (fork()) {
316 # parent
317 exit(0);
318 } else {
319 # child
320 &write_log ("Guardian process id $$\n");
321 $home = (getpwuid($>))[7] || die "No home directory!\n";
322 chdir($home); # go to my homedir
323 setpgrp(0,0); # become process leader
324 close(STDOUT);
325 close(STDIN);
326 close(STDERR);
327 print "Testing...\n";
328 }
329}
330
331sub get_ip {
332 my ($interface) = $_[0];
333 my $ip;
334 open (IFCONFIG, "/bin/netstat -iee |grep $interface -A7 |");
335 while (<IFCONFIG>) {
336 if ($OS eq "FreeBSD") {
337 if (/inet (\d+\.\d+\.\d+\.\d+)/) {
338 $ip = $1;
339 }
340 }
341 if ($OS eq "Linux") {
342 if (/inet addr:(\d+\.\d+\.\d+\.\d+)/) {
343 $ip = $1;
344 }
345 }
346 }
347 close (IFCONFIG);
348 if ($ip eq "") { die "Couldn't figure out the ip address\n"; }
349 $ip;
350}
351
352sub sig_handler_setup {
353 $SIG{TERM} = \&clean_up_and_exit; # kill
354 $SIG{QUIT} = \&clean_up_and_exit; # kill -3
355# $SIG{HUP} = \&flush_and_reload; # kill -1
356}
357
358sub remove_blocks {
359 my $source;
360 my $time = time();
361 foreach $source (keys %hash) {
362 if ($hash{$source} < $time) {
363 &call_unblock ($source, "expiring block of $source\n");
364 delete ($hash{$source});
365 }
366 }
367}
368
369sub call_unblock {
370 my ($source, $message) = @_;
371 &write_log ("$message");
fb30e20c 372 system ("$unblockpath $source");
7c4cc0d8
CS
373}
374
375sub clean_up_and_exit {
376 my $source;
377 &write_log ("received kill sig.. shutting down\n");
378 foreach $source (keys %hash) {
379 &call_unblock ($source, "removing $source for shutdown\n");
380 }
381 exit;
382}
383
384sub load_targetfile {
385 my $count = 0;
386 open (TARG, "$targetfile") or die "Cannot open $targetfile\n";
387 while (<TARG>) {
388 chop;
389 next if (/\#/); #skip comments
390 next if (/^\s*$/); # and blank lines
391 $targethash{$_}=1;
392 $count++;
393 }
394 close (TARG);
395 print "Loaded $count addresses from $targetfile\n";
396}