diff --git a/AN/Tools.pm b/AN/Tools.pm index 23644683..09b4b781 100755 --- a/AN/Tools.pm +++ b/AN/Tools.pm @@ -656,6 +656,7 @@ sub _set_paths backups => "/usr/sbin/striker/backups", 'cgi-bin' => "/var/www/cgi-bin", firewalld_services => "/usr/lib/firewalld/services", + firewalld_zones => "/etc/firewalld/zones/", html => "/var/www/html", skins => "/var/www/html/skins", tools => "/usr/sbin/striker", diff --git a/AN/Tools/System.pm b/AN/Tools/System.pm index 6f0379c5..7ee53946 100755 --- a/AN/Tools/System.pm +++ b/AN/Tools/System.pm @@ -25,6 +25,8 @@ my $THIS_FILE = "System.pm"; # reload_daemon # start_daemon # stop_daemon +# _load_firewalld_zones +# _load_specific_firewalld_zone # _match_port_to_service =pod @@ -438,6 +440,9 @@ sub manage_firewall } } + # Read in all services and store information about them. + #$an->System->_load_firewalld_zones; + # What is the default zone? Read the config file, if possible, as it is several times faster than # invoking 'firewall-cmd'. my $default_zone = ""; @@ -480,96 +485,159 @@ sub manage_firewall { ### TODO: Read /etc/firewalld/zones/${active_zone}.xml with XMLin and for each ### '', read in '$an->data->{path}{directories}{firewalld_services}/foo.xml'. - my $shell_call = $an->data->{path}{exe}{'firewall-cmd'}." --zone=".$active_zone." --list-all"; - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); - - my $output = $an->System->call({shell_call => $shell_call}); - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output }}); - foreach my $line (split/\n/, $output) + if (1) { - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { line => $line }}); - if ($line =~ /services: (.*)$/) + my $zone_file = $an->data->{path}{directories}{firewalld_zones}."/".$active_zone.".xml"; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { zone_file => $zone_file }}); + + if (not -e $zone_file) { - my $services = $an->Words->clean_spaces({ string => $1 }); - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { services => $services }}); - foreach my $service (split/\s/, $services) - { - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { service => $service }}); - push @{$open_services}, $service; - } + die $THIS_FILE." ".__LINE__."; zone config not found: [$zone_file]\n"; } - if ($line =~ /ports: (.*)$/) + my $xml = XML::Simple->new(); + my $body = ""; + eval { $body = $xml->XMLin($zone_file, KeyAttr => { language => 'name', key => 'name' }, ForceArray => [ 'service' ]) }; + if ($@) { - my $open_ports = $an->Words->clean_spaces({ string => $1 }); - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { open_ports => $open_ports }}); - foreach my $port (split/\s/, $open_ports) + chomp $@; + my $error = "[ Error ] - The was a problem reading: [$zone_file]. The error was:\n"; + $error .= "===========================================================\n"; + $error .= $@."\n"; + $error .= "===========================================================\n"; + $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", raw => $error}); + } + else + { + #print Dumper $body; + print "Zone: [$active_zone] open services;\n"; + foreach my $hash_ref (@{$body->{service}}) { - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { port => $port }}); - if ($port =~ /^(\d+)\/tcp/) + my $service = $hash_ref->{name}; + print "- $service\n"; + $an->System->_load_specific_firewalld_zone({service => $hash_ref->{name}}); + } + + foreach my $service (sort {$a cmp $b} keys %{$an->data->{firewalld}{zones}{by_name}}) + { + print "- $service\n"; + my $tcp_ports = ""; + foreach my $port (sort {$a cmp $b} @{$an->data->{firewalld}{zones}{by_name}{$service}{tcp}}) + { + #print "- tcp: [$port]\n"; + $tcp_ports .= $port.", "; + } + $tcp_ports =~ s/, $//; + if ($tcp_ports) { - my $tcp_port = $1; - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { tcp_port => $tcp_port }}); - push @{$open_tcp_ports}, $tcp_port; + print " - TCP Ports: [$tcp_ports]\n"; } - elsif ($port =~ /^(\d+)\/udp/) + + my $udp_ports = ""; + foreach my $port (sort {$a cmp $b} @{$an->data->{firewalld}{zones}{by_name}{$service}{udp}}) { - my $udp_port = $1; - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { udp_port => $udp_port }}); - push @{$open_udp_ports}, $udp_port; + $udp_ports .= $port.", "; } - else + $udp_ports =~ s/, $//; + if ($udp_ports) { - # Bad port. - return("!!error!!"); + print " - UDP Ports: [$udp_ports]\n"; } } } } - - # Convert services to ports. - foreach my $service (sort @{$open_services}) + if (0) { - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { service => $service }}); - - my $shell_call = $an->data->{path}{exe}{'firewall-cmd'}." --info-service ".$service; + my $shell_call = $an->data->{path}{exe}{'firewall-cmd'}." --zone=".$active_zone." --list-all"; $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); - my $output = $an->System->call({shell_call => $shell_call}); - my $this_port = ""; - my $this_protocol = ""; + my $output = $an->System->call({shell_call => $shell_call}); $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output }}); foreach my $line (split/\n/, $output) { $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { line => $line }}); - if ($line =~ /ports: (\d+)\/(.*)$/) + if ($line =~ /services: (.*)$/) { - $this_port = $1; - $this_protocol = $2; - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { - this_port => $this_port, - this_protocol => $this_protocol, - }}); - if ($this_protocol eq "tcp") + my $services = $an->Words->clean_spaces({ string => $1 }); + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { services => $services }}); + foreach my $service (split/\s/, $services) { - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { this_port => $this_port }}); - push @{$open_tcp_ports}, $this_port; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { service => $service }}); + push @{$open_services}, $service; } - elsif ($this_protocol eq "udp") - { - $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { this_port => $this_port }}); - push @{$open_udp_ports}, $this_port; - } - else + } + if ($line =~ /ports: (.*)$/) + { + my $open_ports = $an->Words->clean_spaces({ string => $1 }); + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { open_ports => $open_ports }}); + foreach my $port (split/\s/, $open_ports) { - # What? - return("!!error!!"); + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { port => $port }}); + if ($port =~ /^(\d+)\/tcp/) + { + my $tcp_port = $1; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { tcp_port => $tcp_port }}); + push @{$open_tcp_ports}, $tcp_port; + } + elsif ($port =~ /^(\d+)\/udp/) + { + my $udp_port = $1; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { udp_port => $udp_port }}); + push @{$open_udp_ports}, $udp_port; + } + else + { + # Bad port. + return("!!error!!"); + } } } } - if ((not $this_port) or (not $this_protocol)) + + # Convert services to ports. + foreach my $service (sort @{$open_services}) { - # What? - return("!!error!!"); + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { service => $service }}); + + my $shell_call = $an->data->{path}{exe}{'firewall-cmd'}." --info-service ".$service; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); + + my $output = $an->System->call({shell_call => $shell_call}); + my $this_port = ""; + my $this_protocol = ""; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output }}); + foreach my $line (split/\n/, $output) + { + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { line => $line }}); + if ($line =~ /ports: (\d+)\/(.*)$/) + { + $this_port = $1; + $this_protocol = $2; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { + this_port => $this_port, + this_protocol => $this_protocol, + }}); + if ($this_protocol eq "tcp") + { + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { this_port => $this_port }}); + push @{$open_tcp_ports}, $this_port; + } + elsif ($this_protocol eq "udp") + { + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { this_port => $this_port }}); + push @{$open_udp_ports}, $this_port; + } + else + { + # What? + return("!!error!!"); + } + } + } + if ((not $this_port) or (not $this_protocol)) + { + # What? + return("!!error!!"); + } } } } @@ -1524,6 +1592,207 @@ sub stop_daemon # Private functions # ############################################################################################################# +=head2 _load_firewalld_zones + +This reads in the XML files for all of the firewalld zones. + +It takes no arguments. + +=cut +sub _load_firewalld_zones +{ + my $self = shift; + my $parameter = shift; + my $an = $self->parent; + + my $directory = $an->data->{path}{directories}{firewalld_services}; + $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0018", variables => { directory => $directory }}); + if (not -d $directory) + { + # Missing directory... + return("!!error!!"); + } + local(*DIRECTORY); + opendir(DIRECTORY, $directory); + while(my $file = readdir(DIRECTORY)) + { + next if $file !~ /\.xml$/; + my $full_path = $directory."/".$file; + my $service = ($file =~ /^(.*?)\.xml$/)[0]; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { + full_path => $full_path, + service => $service, + }}); + + $an->System->_load_specific_firewalld_zone({service => $service}); + } + closedir DIRECTORY; + + ### DEBUG stuff... + if (0) + { + foreach my $service (sort {$a cmp $b} keys %{$an->data->{firewalld}{zones}{by_name}}) + { + print "Service name: [$service (".$an->data->{firewalld}{zones}{by_name}{$service}{name}.")]\n"; + + my $tcp_ports = ""; + foreach my $port (sort {$a cmp $b} @{$an->data->{firewalld}{zones}{by_name}{$service}{tcp}}) + { + #print "- tcp: [$port]\n"; + $tcp_ports .= $port.", "; + } + $tcp_ports =~ s/, $//; + if ($tcp_ports) + { + print "- TCP Ports: [$tcp_ports]\n"; + } + + my $udp_ports = ""; + foreach my $port (sort {$a cmp $b} @{$an->data->{firewalld}{zones}{by_name}{$service}{udp}}) + { + $udp_ports .= $port.", "; + } + $udp_ports =~ s/, $//; + if ($udp_ports) + { + print "- UDP Ports: [$udp_ports]\n"; + } + } + } + + if (0) + { + foreach my $protocol ("tcp", "udp") + { + foreach my $port (sort {$a <=> $b} keys %{$an->data->{firewalld}{zones}{by_port}{$protocol}}) + { + my $service = $an->data->{firewalld}{zones}{by_port}{$protocol}{$port}; + print "$port / $protocol -> [$service] (".$an->data->{firewalld}{zones}{by_name}{$service}{name}.")\n"; + } + } + } + + return(0); +} + +=head2 _load_specific_firewalld_zone + +This takes the name of a service (with or without the C<< .xml >> suffix) and reads it into the C<< $an->data >> hash. + +Data will be stored as: + +* C<< firewalld::zones::by_name::::name = Short name >> +* C<< firewalld::zones::by_name::::tcp = >> +* C<< firewalld::zones::by_name::::tcp = >> +* C<< firewalld::zones::by_port:::: = >> + +The 'C<< service >> name is the service file name, minus the C<< .xml >> suffix. + +If there is a problem, C<< !!error!! >> will be returned. + +Parameters; + +=head3 service (required) + +This is the name of the service to read in. It expects the file to be in the C<< path::directories::firewalld_services >> diretory. If the service name doesn't end in C<< .xml >>, that suffix will be added automatically. + +=cut +sub _load_specific_firewalld_zone +{ + my $self = shift; + my $parameter = shift; + my $an = $self->parent; + + my $service = defined $parameter->{service} ? $parameter->{service} : ""; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { service => $service }}); + + if (not $service) + { + # No service name + return("!!error!!"); + } + + if ($service !~ /\.xml$/) + { + $service .= ".xml"; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { service => $service }}); + } + + my $full_path = $an->data->{path}{directories}{firewalld_services}."/".$service; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { full_path => $full_path }}); + if (not -e $full_path) + { + # File not found + return("!!error!!"); + } + + my $xml = XML::Simple->new(); + my $body = ""; + eval { $body = $xml->XMLin($full_path, KeyAttr => { language => 'name', key => 'name' }, ForceArray => [ 'port' ]) }; + if ($@) + { + chomp $@; + my $error = "[ Error ] - The was a problem reading: [$full_path]. The error was:\n"; + $error .= "===========================================================\n"; + $error .= $@."\n"; + $error .= "===========================================================\n"; + $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", raw => $error}); + } + else + { + #print Dumper $body; + my $name = $body->{short}; + $an->data->{firewalld}{zones}{by_name}{$service}{name} = $name; + if ((not defined $an->data->{firewalld}{zones}{by_name}{$service}{tcp}) or (ref($an->data->{firewalld}{zones}{by_name}{$service}{tcp}) ne "ARRAY")) + { + $an->data->{firewalld}{zones}{by_name}{$service}{tcp} = []; + } + if ((not defined $an->data->{firewalld}{zones}{by_name}{$service}{udp}) or (ref($an->data->{firewalld}{zones}{by_name}{$service}{udp}) ne "ARRAY")) + { + $an->data->{firewalld}{zones}{by_name}{$service}{udp} = []; + } + + #print "Name: [$name], file: [$file]\n"; + foreach my $hash_ref (@{$body->{port}}) + { + my $this_port = $hash_ref->{port}; + my $this_protocol = $hash_ref->{protocol}; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { + this_port => $this_port, + this_protocol => $this_protocol, + }}); + + # Is this a range? + if ($this_port =~ /^(\d+)-(\d+)$/) + { + # Yup. + my $start = $1; + my $end = $2; + $an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { + start => $start, + end => $end, + }}); + #print "- Range - $this_port ($start -> $end)...\n"; + foreach my $port ($start..$end) + { + #print "- $port / $this_protocol\n"; + $an->data->{firewalld}{zones}{by_port}{$this_protocol}{$port} = $service; + push @{$an->data->{firewalld}{zones}{by_name}{$service}{$this_protocol}}, $port; + } + } + else + { + # Nope + #print "- $this_port / $this_protocol\n"; + $an->data->{firewalld}{zones}{by_port}{$this_protocol}{$this_port} = $service; + push @{$an->data->{firewalld}{zones}{by_name}{$service}{$this_protocol}}, $this_port; + } + } + } + + return(0); +} + =head2 _match_port_to_service This takes a port number and returns the service name, if it matches one of them. Otherwise it returns an empty string.