--- /dev/null
+#!/usr/bin/perl
+
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+# Copyright (C) 2020 The IPFire Team #
+# #
+###############################################################################
+
+# enable the following only for debugging purposes
+use strict;
+#use warnings;
+use CGI qw/:standard/;
+#use CGI::Carp 'fatalsToBrowser';
+use Sort::Naturally;
+use Socket;
+use IPC::Open2;
+
+require '/var/ipfire/general-functions.pl';
+require "${General::swroot}/lang.pl";
+require "${General::swroot}/header.pl";
+require "${General::swroot}/location-functions.pl";
+
+############################################################################
+# Constants
+############################################################################
+
+############################################################################
+# Stylesheet
+############################################################################
+
+my $css = <<END
+<style>
+ table {
+ width: 100%;
+ border-collapse: collapse;
+ table-layout: fixed;
+ }
+
+ tr {
+ height: 2em;
+ }
+
+ td.slightlygrey {
+ background-color: #F0F0F0;
+ }
+
+ td.h {
+ background-color: grey;
+ color: white;
+ font-weight: 800;
+ }
+
+ td.green {
+ background-color: $Header::colourgreen;
+ }
+
+ td.red {
+ background-color: $Header::colourred;
+ }
+
+ td.blue {
+ background-color: $Header::colourblue;
+ }
+
+ td.orange {
+ background-color: $Header::colourorange;
+ }
+
+ td.yellow {
+ background-color: $Header::colouryellow;
+ }
+
+ td.topleft {
+ background-color: white;
+ border-top-style: none;
+ border-left-style: none;
+ }
+
+ td.disabled {
+ background-color: #cccccc;
+ }
+
+ td.textcenter {
+ text-align: center;
+ }
+
+ button {
+ margin-top: 1em;
+ }
+</style>
+END
+;
+
+############################################################################
+# Function prototypes
+############################################################################
+
+sub error();
+sub show_query( $ );
+sub show_result( $$$$ );
+sub lookup_url( $ );
+sub check_url( $$@ );
+sub check_ipblacklist( @ );
+sub check_geoip( @ );
+
+###############################################################################
+# Configuration variables
+###############################################################################
+
+my $dhcp_settings = "${General::swroot}/dhcp/settings";
+my $ethernet_settings = "${General::swroot}/ethernet/settings";
+my $proxy_settings = "${General::swroot}/proxy/settings";
+my $blacklist_settings = "${General::swroot}/ipblacklist/settings";
+my $location_settings = "${General::swroot}/firewall/locationblock";
+my $ipset_save_dir = "/var/lib/ipblacklist";
+
+###############################################################################
+# Variables
+###############################################################################
+
+my %cgiparams = ('ACTION' => '');
+my $errormessage = '';
+my $check_address = '';
+my $scheme = '';
+my $url = '';
+my $path = '';
+my $valid_fqdn = 0;
+my $valid_ip = 0;
+my %ethernet_settings;
+my %results = ();
+my %color;
+
+###############################################################################
+# Start of code
+###############################################################################
+
+# Read all parameters for site
+Header::getcgihash( \%cgiparams);
+General::readhash( $ethernet_settings, \%ethernet_settings );
+
+# Show Headers
+
+Header::showhttpheaders();
+
+# Process actions
+
+if ($cgiparams{'ACTION'} eq "$Lang::tr{'adrschk check'}")
+{
+ # Check Button
+
+ $url = $cgiparams{'check address'} || '';
+
+ $url =~ s|(^\w+)://(?:.*\@)?||; # remove scheme and authority
+ $scheme = $1 || 'http';
+ ($check_address, $path) = split '/', $url;
+
+ $valid_ip = General::validip( $check_address );
+ $valid_fqdn = General::validfqdn( $check_address );
+
+ $url = "$scheme://$check_address/$path";
+
+ # Can't check path in encrypted queries
+ $path = '' if ($scheme eq 'https');
+
+ if (not $valid_ip and not $valid_fqdn)
+ {
+ $errormessage .= "$Lang::tr{'adrschk invalid check address'}: $check_address<br>"
+ }
+}
+
+# Show site
+
+Header::openpage( $Lang::tr{'adrschk address check'}, 1, $css );
+Header::openbigbox( '100%', 'left' );
+
+error() if ($errormessage);
+show_query( $url );
+show_result( $scheme, $check_address, $path, $valid_ip ) if ($valid_ip or $valid_fqdn);
+
+# End of page
+
+Header::closebigbox();
+Header::closepage();
+
+exit 0;
+
+
+#------------------------------------------------------------------------------
+# sub show_query( url)
+#
+# Displays the address query form
+#------------------------------------------------------------------------------
+
+sub show_query( $ )
+{
+ my ($url) = @_;
+
+ $url = Header::escape( $url );
+
+ Header::openbox('100%', 'left', $Lang::tr{'adrschk address'});
+
+ print <<END;
+<form method='post' action='$ENV{'SCRIPT_NAME'}'>
+ <table style='width:100%' border='0'>
+ <tr>
+ <td style='width:20em'>$Lang::tr{'adrschk address or url'}</td>
+ <td><input type='text' name='check address' id='check address' value='$url' style='width:40em'></td>
+ </tr>
+ <tr>
+ <td colspan='2' display:inline align='right'><input type='submit' name='ACTION' value='$Lang::tr{'adrschk check'}'></td>
+ </tr>
+ </table>
+</form>
+END
+
+ Header::closebox();
+}
+
+
+#------------------------------------------------------------------------------
+# sub show_result( scheme, address, is_validip )
+#
+# Displays the result of the query
+#------------------------------------------------------------------------------
+
+sub show_result( $$$$ )
+{
+ my ($scheme, $address, $path, $is_validip) = @_;
+
+ my @ip_addresses;
+
+ Header::openbox('100%', 'left', "$Lang::tr{'adrschk check result'} $address" );
+
+ if ($is_validip)
+ {
+ @ip_addresses = ( Network::ip_remove_zero( $address ) );
+ }
+ else
+ {
+ @ip_addresses = lookup_url( $address );
+ }
+
+ check_urlfilter( $address, $path, @ip_addresses ) if ($scheme =~ m/^http/);
+
+ check_ipblacklist( @ip_addresses );
+
+ check_location( @ip_addresses );
+
+ Header::closebox();
+}
+
+
+#------------------------------------------------------------------------------
+# sub lookup_url( url )
+#
+# Looks up the URL in the nameservers
+#
+# Parameters:
+# url The URL to be looked up
+#
+# Returns:
+# The list of IP Addresses returned by the nameservers
+#------------------------------------------------------------------------------
+
+sub lookup_url( $ )
+{
+ my ($url) = @_;
+
+ my %dhcp_settings;
+ my %servers_per_colour;
+ my %addresses;
+ my $authenticated = 0;
+ my $signed = 0;
+
+ General::readhash( $dhcp_settings, \%dhcp_settings );
+
+ print <<END;
+ <h2>$Lang::tr{'adrschk dns lookup'}</h2>
+END
+
+ # Get the addresses of the DNS Servers
+ # The servers sent to clients by DHCP
+
+ foreach my $dns_server ( 'DNS1_GREEN', 'DNS2_GREEN', 'DNS1_BLUE', 'DNS2_BLUE','DNS1_ORANGE', 'DNS2_ORANGE' )
+ {
+ next unless $dhcp_settings{$dns_server};
+
+ my ($colour) = $dns_server =~ m/_(\w+)$/;
+
+ next unless ($dhcp_settings{"ENABLE_$colour"} eq 'on');
+
+ $colour = lc $colour;
+ $servers_per_colour{$colour}++;
+
+ $results{$dns_server}{server_address} = $dhcp_settings{$dns_server};
+ $results{$dns_server}{query_address}{authenticated} = 0;
+ $results{$dns_server}{query_address}{signed} = 0;
+ }
+
+ # The servers used by IPFire
+
+ my $count = 1;
+
+ foreach my $nameserver (nsort General::get_nameservers())
+ {
+ my $interface = "DNS${count}_RED";
+ $count++;
+
+ $servers_per_colour{red}++;
+ $results{$interface}{server_address} = $nameserver;
+ $results{$interface}{query_address}{authenticated} = 0;
+ $results{$interface}{query_address}{signed} = 0;
+ }
+
+ if (not keys %results)
+ {
+ print "<p><strong>$Lang::tr{'adrschk no dns servers'}</strong></p>\n";
+
+ return;
+ }
+
+ my $host = $url;
+
+ # The URL doesn't necessarily refer to a host, so we have to keep removing
+ # prefixes until we run out or the host resolves.
+
+ until (%addresses)
+ {
+ # Look up the domain in the DNS servers
+
+ foreach my $dns_server ( keys %results )
+ {
+ my $server_address = $results{$dns_server}{server_address};
+
+ open DIG, '-|', "dig +dnssec \@$server_address $host";
+
+ foreach my $line (<DIG>)
+ {
+ if ($line =~ m/^;; flags:([a-z ]*);/)
+ {
+ foreach my $flag (split /\s+/, $1)
+ {
+ if ($flag eq 'ad')
+ {
+ $results{$dns_server}{authenticated} = 1;
+ $authenticated = 1;
+ }
+ }
+ }
+ elsif ($line =~ m/(\S+)\.\s+\d+\s+IN\s+A\s+([\d\.]*)/)
+ {
+ my ($found, $address) = ($1, $2);
+
+ if ($host =~ m/$found/)
+ {
+ $results{$dns_server}{query_address}{$address} = 1;
+ $addresses{$address} = 1;
+ }
+ }
+ elsif ($line =~ m/^$host\.\s+\d+\s+IN\s+RRSIG/)
+ {
+ $results{$dns_server}{signed} = 1;
+ $signed = 1;
+ }
+ }
+
+ close DIG;
+ }
+
+ unless (%addresses)
+ {
+ # Didn't resolve - remove a prefix.
+
+ $host =~ s/^.+?\.//;
+
+ last if ($host !~ m/\w+\.\w+/);
+ }
+ }
+
+ # Display the results
+
+ my $colspan = keys %addresses;
+
+ print <<END;
+ <table width='100%' cellspacing='1' class='tbl'>
+ <tr>
+ <td class='h textcenter'>$Lang::tr{'dns server'}</td>
+ <td class='h textcenter' colspan='$colspan'>$Lang::tr{'adrschk address'}</td>
+ <td class='h textcenter' colspan='2'>DNSSEC</td>
+ </tr>
+END
+
+ foreach my $dns_server ( sort {$results{$b} cmp $results{$a}} keys %results )
+ {
+ my ($colour) = $dns_server =~ m/_(\w+)/;
+ $colour = lc $colour;
+ print "<tr><td class='h textcenter $colour'>$results{$dns_server}{server_address}</td>";
+
+ foreach my $address (nsort keys %addresses )
+ {
+ my $ipinfo = "<a href='/cgi-bin/ipinfo.cgi?ip=$address'>$address</a>";
+
+ print "<td class='textcenter'>";
+ print $results{$dns_server}{query_address}{$address} ? $ipinfo : '-';
+ print "</td>";
+ }
+
+ if ($results{$dns_server}{signed} or $results{$dns_server}{authenticated})
+ {
+ if ($results{$dns_server}{signed})
+ {
+ print "<td class='textcenter'>$Lang::tr{'adrschk signed'}</td>";
+ }
+ else
+ {
+ print "<td class='textcenter yellow'>-</td>"
+ }
+
+ if ($results{$dns_server}{authenticated})
+ {
+ print "<td class='textcenter'>$Lang::tr{'adrschk authenticated'}</td>";
+ }
+ else
+ {
+ print "<td class='textcenter yellow'>-</td>"
+ }
+ }
+ else
+ {
+ print "<td class='textcenter yellow' colspan='2'>-</td>"
+ }
+
+ print "</tr>\n";
+ }
+
+ print <<END;
+ </table><br>
+END
+
+ return sort (keys %addresses);
+}
+
+
+#------------------------------------------------------------------------------
+# sub check_urlfilter( url, path, address... )
+#
+# Checks to see if the URL filter blocks the site
+#------------------------------------------------------------------------------
+
+sub check_urlfilter( $$@ )
+{
+ my ($url, $path, @addresses) = @_;
+
+ my @checks;
+ my %proxy_settings;
+
+ General::readhash( $proxy_settings, \%proxy_settings );
+
+ print <<END;
+ <h2>$Lang::tr{'adrschk url filter'}</h2>
+END
+
+ if ($proxy_settings{ENABLE_FILTER} ne 'on')
+ {
+ print "<p>$Lang::tr{'adrschk url filter disabled'}</p>\n";
+ return;
+ }
+
+ print <<END;
+ <table width='100%' cellspacing='1' class='tbl'>
+ <tr>
+ <td></td>
+END
+
+ foreach my $dest ($url, @addresses)
+ {
+ push @checks, $dest;
+ push @checks, "$dest/$path" if ($path);
+ }
+
+ foreach my $interface ('BLUE', 'GREEN', 'ORANGE')
+ {
+ if (exists $ethernet_settings{"${interface}_ADDRESS"})
+ {
+ my $colour = lc $interface;
+
+ print "<td class='h textcenter $colour'>$Lang::tr{$colour}</td>"
+ }
+ }
+
+ print <<END;
+ </tr>
+END
+
+my ($from_sqgd, $to_sqgd);
+my $childpid = open2( $from_sqgd, $to_sqgd, 'squidGuard -d 2>/dev/null' );
+
+foreach my $check (@checks)
+{
+ print <<END;
+ <tr>
+ <td class='h'>$check</td>
+END
+
+ foreach my $interface ('BLUE', 'GREEN', 'ORANGE')
+ {
+ if (exists $ethernet_settings{"${interface}_ADDRESS"})
+ {
+ print $to_sqgd "http://$check ethernet_settings{${interface}_ADDRESS}/- - GET\n";
+
+ my $line = <$from_sqgd>;
+
+ if ($line =~ m/OK rewrite-url/)
+ {
+ print "<td class='yellow'>$Lang::tr{'adrschk blocked'}";
+
+ if ($line =~ m/category=([^&]*)/)
+ {
+ print " - $1";
+ }
+
+ print "</td>";
+ }
+ else
+ {
+ print "<td>$Lang::tr{'adrschk not blocked'}</td>";
+ }
+ }
+ }
+
+ print "</tr>\n";
+ }
+
+ print "</table><br>\n";
+
+ close $to_sqgd;
+ close $from_sqgd;
+
+ kill 'TERM', $childpid;
+
+ waitpid( $childpid, 0 );
+}
+
+
+#------------------------------------------------------------------------------
+# sub check_ipblacklist( address... )
+#
+# Checks to see if an address is listed in an IP Blacklist
+#------------------------------------------------------------------------------
+
+sub check_ipblacklist( @ )
+{
+ my @addresses = @_;
+
+ my %blacklist_settings;
+
+ return unless (-r $blacklist_settings);
+
+ General::readhash( $blacklist_settings, \%blacklist_settings );
+
+ print <<END;
+ <h2>$Lang::tr{'adrschk ipblacklist'}</h2>
+END
+
+ if ($blacklist_settings{'ENABLE'} eq 'off')
+ {
+ print "<p>$Lang::tr{'adrschk ipblacklists disabled'}</p>\n";
+ return;
+ }
+
+ print <<END;
+<table width='100%' cellspacing='1' class='tbl'>
+ <tr>
+ <td> </td>
+END
+
+ foreach my $address (@addresses)
+ {
+ print "<td class='h textcenter'>$address</td>";
+ }
+
+ print <<END;
+ </tr>
+END
+
+ my @sets;
+
+ opendir SETS, $ipset_save_dir or die "Can't open IPSet save directory: $!";
+
+ foreach my $set (readdir SETS)
+ {
+ next unless ($set =~ m/[^\.]\w+\.conf/);
+
+ push @sets, $set;
+ }
+
+ closedir SETS;
+
+ foreach my $set (sort @sets)
+ {
+ my %blocked;
+ my $type = 'ip';
+
+ open SET, '<', "$ipset_save_dir/$set" or die "Can't open IPSet save file $set";
+
+ foreach my $line (<SET>)
+ {
+ next unless ($line =~ m|^add \w+ ([\d\./]+)$|);
+
+ $blocked{$1} = 1;
+ $type = 'net' if ($line =~ m|/\d+$|);
+ }
+
+ close SET;
+
+ $set =~ s/\.conf$//;
+
+ print "<tr>\n<td class='h'>$set</td>";
+
+ foreach my $address (nsort @addresses)
+ {
+ if ($type eq 'ip')
+ {
+ if (exists $blocked{$address})
+ {
+ print "<td class='textcenter yellow'>$Lang::tr{'adrschk blocked'}</td>";
+ }
+ else
+ {
+ print "<td class='textcenter'>$Lang::tr{'adrschk not blocked'}</td>";
+ }
+ }
+ else
+ {
+ my $found = 0;
+
+ foreach my $net (keys %blocked)
+ {
+ if (Network::ip_address_in_network( $address, $net ))
+ {
+ print "<td class='textcenter yellow'>$Lang::tr{'adrschk blocked'}</td>";
+ $found = 1;
+ last;
+ }
+ }
+
+ print "<td class='textcenter'>$Lang::tr{'adrschk not blocked'}</td>" unless ($found);
+ }
+ }
+
+ print "</tr>\n";
+ }
+
+ print "</table><br>\n";
+}
+
+
+#------------------------------------------------------------------------------
+# sub check_location( address... )
+#
+# Checks to see if an address blocked by the geoip
+#------------------------------------------------------------------------------
+
+sub check_location( @ )
+{
+ my @addresses = @_;
+
+ my %location_settings;
+
+ return unless (-r $location_settings);
+
+ General::readhash( $location_settings, \%location_settings );
+
+ print <<END;
+ <h2>$Lang::tr{'adrschk location'}</h2>
+END
+
+ if ($location_settings{'LOCATIONBLOCK_ENABLED'} ne 'on')
+ {
+ print "<p>$Lang::tr{'adrschk location disabled'}</p>\n";
+ }
+
+ print <<END;
+<table width='100%' cellspacing='1' class='tbl'>
+ <tr>
+ <td> </td>
+ <td class='h' style='width:2em;'></td>
+ <td class='h textcenter'>$Lang::tr{'country'}</td>
+ <td class='h' style='width:5em;'></td>
+ <td class='h textcenter'>ASN</td>
+ <td class='h textcenter'>$Lang::tr{'adrschk blocked'}</td>
+ </tr>
+END
+
+ my $db = Location::Functions::init();
+
+ foreach my $address (@addresses)
+ {
+ my $ccode = Location::Functions::lookup_country_code( $address );
+ my $flag = Location::Functions::get_flag_icon( $ccode );
+ my $cname = Location::Functions::get_full_country_name( $ccode );
+ my $asn = Location::Functions::lookup_asn( $address );
+ my $asnname = Location::Functions::get_as_name( $asn );
+ my $blocked;
+
+ if ($location_settings{'LOCATIONBLOCK_ENABLED'} ne 'on')
+ {
+ $blocked = "<td class='textcenter'>-</td>";
+ }
+ elsif ($location_settings{$ccode} eq 'on')
+ {
+ $blocked = "<td class='textcenter yellow'>$Lang::tr{'adrschk blocked'}</td>";
+ }
+ else
+ {
+ $blocked = "<td class='textcenter'>$Lang::tr{'adrschk not blocked'}</td>";
+ }
+
+ print <<END;
+ <tr>
+ <td class='h textcenter'>$address</td>
+ <td><a href='country.cgi#$ccode'><img src="$flag" border="0" alt="$ccode" title="$ccode"></a></td>
+ <td>$cname</td>
+ <td>$asn</td>
+ <td>$asnname</td>
+ $blocked
+ </tr>
+END
+ }
+
+ print <<END;
+ </table><br>
+END
+}
+
+
+#------------------------------------------------------------------------------
+# sub error()
+#
+# Shows error messages
+#------------------------------------------------------------------------------
+
+sub error()
+{
+ Header::openbox('100%', 'left', $Lang::tr{'error messages'});
+ print "<class name='base'>$errormessage\n";
+ print " </class>\n";
+ Header::closebox();
+}