diff --git a/modules/util/Util/ufw.pm b/modules/util/Util/ufw.pm new file mode 100644 index 0000000000000000000000000000000000000000..748d87af66fd7fb906141ec1526fc8e8baaccf99 --- /dev/null +++ b/modules/util/Util/ufw.pm @@ -0,0 +1,328 @@ +#!/usr/bin/perl + +use strict; + +package Util::ufw; + +sub new +{ + my $class= shift; + + my $self= + { + status => undef, + }; + bless $self, $class; + $self->set(@_); + $self; +} + +sub set +{ + my $self= shift; + my %pars= @_; + + foreach my $par (keys %pars) + { + $self->{$par}= $pars{$par}; + } +} + +sub status +{ + my $self= shift; + + $self= Util::ufw->new(@_) if ($self eq 'Util::ufw'); + + open (UFW, '-|:utf8', 'ufw', 'status', 'numbered') or die; + + my $st= 0; + + my @rules= (); $self->{rules}= \@rules; + my %si4= (); $self->{src_ipv4}= \%si4; + my %si4c= (); $self->{src_ipv4_cidr}= \%si4c; + my ($rule_number, $text); + while (<UFW>) + { + chop; + # print __LINE__, " ufw: st=[$st] [$_]\n"; + + if ($st == 0) + { + if ($_ =~ /^Status: (.+)/) { $self->{status}= $1; } + $st++; + } + elsif ($st == 1) + { + if ($_ =~ /^[ \-]+$/) { $st= 2; } + } + elsif ($st == 2) # rules section + { + if ($_ eq '') { $st= 3; } + elsif ($_ =~ /^\[\s*(\d+)\]\s*(.*)/) + { + ($rule_number, $text)= ($1, $2); + + my %rule= ( rule_number => $rule_number, line => $_, addressing => 'ipv4' ); + + my @t= split(' ', $text); + my $rst= 0; + while (my $t= shift (@t)) + { + if ($rst == 0) + { + $rule{dst}= $t; + if ($t[0] eq '(v6)') { $rule{addressing}= 'ipv6'; shift(@t); } + $rst= 1; + } + elsif ($rst == 1) + { + $t .= ' '. shift(@t) if ($t[0] eq 'IN' || $t[0] eq 'OUT'); + my $policy= 'unknown'; + + if ($t eq 'DENY IN') { $policy= 'deny' } + elsif ($t eq 'REJECT IN') { $policy= 'reject' } + elsif ($t eq 'ALLOW IN') { $policy= 'allow' } + + $rule{action}= $t; + $rule{policy}= $policy; + $rst= 2; + } + elsif ($rst == 2) + { + $rule{src}= $t; + if ($t[0] eq '(v6)') { $rule{addressing}= 'ipv6'; shift(@t); } + + if ($t =~ /^[\d\.\/]+$/) + { + if ($t =~ /\//) { $rule{is_ipv4_cidr}= 1; push (@{$si4c{$t}}, $rule_number); } + else { $rule{is_ipv4}= 1; push (@{$si4{$t}}, $rule_number); } + } + elsif ($t eq 'Anywhere') { $rule{is_world}= 1; } + else { $rule{unkown_src}= 1; } + + $rst= 3; + } + elsif ($rst == 3) + { + if ($t eq '(log)') { $rule{log}= 1; } + else { push (@{$rule{junk}}, $t) } + } + } + push (@rules, \%rule); + } + else + { + print __LINE__, "unknown line=[$_]\n"; + } + } + } + + $self; +} + +sub has_rules_ipv4 +{ + my $self= shift; + my $ipv4= shift; + + if (exists ($self->{src_ipv4}->{$ipv4})) + { + return ($self->{src_ipv4}->{$ipv4}); + } + + return undef; +} + +sub get_rule +{ + my $self= shift; + my $number= shift; + + return ($self->{rules}->[$number-1]); +} + +sub block +{ + my $self= shift; + my $ip= shift; + + my $c= $self->{config}; + # print __LINE__, " c: ", main::Dumper($c); + + ufw($c->{block_policy}, $ip, $c->{log}, $c->{insert_position}); +} + +sub remove_rule +{ + my $self= shift; + my $num= shift; + my @cmd= ('/usr/sbin/ufw', '--force', 'delete', $num); + print __LINE__, " remove_rule: ", join(' ', @cmd), "\n"; + system (@cmd); +} + +sub ufw +{ + my $action= shift; + my $ip= shift; + my $log= shift; + my $pos= shift; + + my @cmd= ('/usr/sbin/ufw'); + push (@cmd, 'insert', $pos) if (defined ($pos)); + push (@cmd, $action); + push (@cmd, 'log') if ($log); + push (@cmd, 'from', $ip); + print __LINE__, ' ', scalar localtime(time()), ' cmd: ', join(' ', @cmd), "\n"; + system(@cmd); +} + +=head2 $rule_number_list= $self->in_firewall_rules($addr); + +Check if a given address is listed in the firwall rules and return the +list of rules, if something matches. + +TODO: we need probably another element in the return values indication +what type of match this was, e.g. if $addr was a cidr block and it matched +cidr blocks or "collected" several explicit ip addresses or even both. +Also, if $addr was a plain ipv4 address and it matches one or more +existiing cidr rules whould be of interesst. Maybe this is getting too +complicated after all and should be handled in different methods instead +of just one. + +=cut + +sub in_firewall_rules +{ + my $self= shift; + my $ip= shift; + + my @rule_list; + if ($ip =~ /^[\d\.\/]+$/) + { + if ($ip =~ /\//) + { # TODO: addr to check is cidr + # later... + if (exists ($self->{src_ipv4_cidr}->{$ip})) + { + my $rule_number_list= $self->{src_ipv4_cidr}->{$ip}; + push (@rule_list, @$rule_number_list); + } + # TO CONSIDER: collect all matching IPv4 addresses and deliver them? + } + else + { # plain vanilla ipv4 address + # print __LINE__, " ip=[$ip]\n"; + if (exists ($self->{src_ipv4}->{$ip})) + { + my $rule_number_list= $self->{src_ipv4}->{$ip}; + push (@rule_list, @$rule_number_list); + } + + # TODO: also check if the ipv4 address to check is convered by a ipv4 cidr block + my $sc= $self->{src_ipv4_cidr}; + if (defined ($sc)) + { + my @sc= keys %$sc; + foreach my $cidr (@sc) + { + my $res= Net::CIDR::cidrlookup($ip, $cidr); + if ($res) { push (@rule_list, @{$sc->{$cidr}}); } # could the be more than one match? + } + } + } + } + + \@rule_list; +} + +sub in_whitelist +{ + my $self= shift; + my $ip= shift; + + $self->load_whitelists() unless ($self->{_whitelists_loaded}); + + if ($ip =~ /^[\d\.\/]+$/) + { + if ($ip =~ /\//) + { # CIDR .... later... + } + else + { + if (exists ($self->{whitelist_ipv4}) && exists ($self->{whitelist_ipv4}->{$ip})) + { + return 'ipv4'; + } + + my $wl_cidr= $self->{whitelist_ipv4_cidr}; + if (defined ($wl_cidr)) + { + my $res= Net::CIDR::cidrlookup($ip, keys %$wl_cidr); + return 'ipv4_cidr' if ($res); + } + } + } + + undef; +} + +sub load_whitelists +{ + my $self= shift; + + my $c= $self->{config}; + return undef unless (defined ($c)); + + if (exists ($c->{whitelist})) + { + $self->add_to_whitelist(@{$c->{whitelist}}); + } + + if (exists ($c->{whitelist_file})) + { + if (open (FI, '<:utf8', $c->{whitelist_file})) + { + my @ips; + while (<FI>) + { + chop; + next if ($_ =~ /^#/); + $_=~ s/[ #].*//; + push (@ips, $_); + } + $self->add_to_whitelist(@ips); + } + else + { # no reason to die + print __LINE__, " can't read whitlist_file=[$c->{whitelist_file}]\m"; + } + } + + $self->{_whitelists_loaded}= 1; +} + +sub add_to_whitelist +{ + my $self= shift; + my @addr= @_; + + foreach my $ip (@addr) + { + if ($ip =~ /^[\d\.]+\/\d+$/) + { + $self->{whitelist_ipv4_cidr}->{$ip}++; + } + elsif ($ip =~ /^[\d\.]+$/) + { + $self->{whitelist_ipv4}->{$ip}++; + } + else + { + print __LINE__, " unknown address format: [$ip]\n"; + } + } +} + +1;