From: Tim FitzGeorge Date: Sat, 15 Feb 2020 14:15:17 +0000 (+0000) Subject: Address check: Check URL or IP Address X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fheads%2Faddresscheck;p=people%2Ftimf%2Fipfire-2.x.git Address check: Check URL or IP Address Signed-off-by: Tim FitzGeorge --- diff --git a/config/menu/60-ipfire.menu b/config/menu/60-ipfire.menu index 8b4c13950..35e513cac 100644 --- a/config/menu/60-ipfire.menu +++ b/config/menu/60-ipfire.menu @@ -3,6 +3,11 @@ 'title' => "Pakfire", 'enabled' => 1, }; + $subipfire->{'90.addresscheck'} = {'caption' => $Lang::tr{'adrschk address check'}, + 'uri' => '/cgi-bin/addresscheck.cgi', + 'title' => $Lang::tr{'adrschk address check'}, + 'enabled' => 1, + }; $subipfire->{'99.help'} = {'caption' => $Lang::tr{'help'}, 'uri' => '/cgi-bin/help.cgi', 'title' => "$Lang::tr{'help'}", diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface index 2e299dac5..2c9c5c9fc 100644 --- a/config/rootfiles/common/web-user-interface +++ b/config/rootfiles/common/web-user-interface @@ -1,3 +1,4 @@ +srv/web/ipfire/cgi-bin/addresscheck.cgi srv/web/ipfire/cgi-bin/aliases.cgi #srv/web/ipfire/cgi-bin/asterisk #srv/web/ipfire/cgi-bin/asterisk/calls.cgi diff --git a/html/cgi-bin/addresscheck.cgi b/html/cgi-bin/addresscheck.cgi new file mode 100755 index 000000000..168b720a9 --- /dev/null +++ b/html/cgi-bin/addresscheck.cgi @@ -0,0 +1,764 @@ +#!/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 . # +# # +# 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 = < + 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; + } + +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
" + } +} + +# 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 < + + + + + + + + +
$Lang::tr{'adrschk address or url'}
+ +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 <$Lang::tr{'adrschk dns lookup'} +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 "

$Lang::tr{'adrschk no dns servers'}

\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 () + { + 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 < + + $Lang::tr{'dns server'} + $Lang::tr{'adrschk address'} + DNSSEC + +END + + foreach my $dns_server ( sort {$results{$b} cmp $results{$a}} keys %results ) + { + my ($colour) = $dns_server =~ m/_(\w+)/; + $colour = lc $colour; + print "$results{$dns_server}{server_address}"; + + foreach my $address (nsort keys %addresses ) + { + my $ipinfo = "$address"; + + print ""; + print $results{$dns_server}{query_address}{$address} ? $ipinfo : '-'; + print ""; + } + + if ($results{$dns_server}{signed} or $results{$dns_server}{authenticated}) + { + if ($results{$dns_server}{signed}) + { + print "$Lang::tr{'adrschk signed'}"; + } + else + { + print "-" + } + + if ($results{$dns_server}{authenticated}) + { + print "$Lang::tr{'adrschk authenticated'}"; + } + else + { + print "-" + } + } + else + { + print "-" + } + + print "\n"; + } + + print <
+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 <$Lang::tr{'adrschk url filter'} +END + + if ($proxy_settings{ENABLE_FILTER} ne 'on') + { + print "

$Lang::tr{'adrschk url filter disabled'}

\n"; + return; + } + + print < + + +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 "$Lang::tr{$colour}" + } + } + + print < +END + +my ($from_sqgd, $to_sqgd); +my $childpid = open2( $from_sqgd, $to_sqgd, 'squidGuard -d 2>/dev/null' ); + +foreach my $check (@checks) +{ + print < + $check +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 "$Lang::tr{'adrschk blocked'}"; + + if ($line =~ m/category=([^&]*)/) + { + print " - $1"; + } + + print ""; + } + else + { + print "$Lang::tr{'adrschk not blocked'}"; + } + } + } + + print "\n"; + } + + print "
\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 <$Lang::tr{'adrschk ipblacklist'} +END + + if ($blacklist_settings{'ENABLE'} eq 'off') + { + print "

$Lang::tr{'adrschk ipblacklists disabled'}

\n"; + return; + } + + print < + +   +END + + foreach my $address (@addresses) + { + print "$address"; + } + + print < +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 () + { + next unless ($line =~ m|^add \w+ ([\d\./]+)$|); + + $blocked{$1} = 1; + $type = 'net' if ($line =~ m|/\d+$|); + } + + close SET; + + $set =~ s/\.conf$//; + + print "\n$set"; + + foreach my $address (nsort @addresses) + { + if ($type eq 'ip') + { + if (exists $blocked{$address}) + { + print "$Lang::tr{'adrschk blocked'}"; + } + else + { + print "$Lang::tr{'adrschk not blocked'}"; + } + } + else + { + my $found = 0; + + foreach my $net (keys %blocked) + { + if (Network::ip_address_in_network( $address, $net )) + { + print "$Lang::tr{'adrschk blocked'}"; + $found = 1; + last; + } + } + + print "$Lang::tr{'adrschk not blocked'}" unless ($found); + } + } + + print "\n"; + } + + print "
\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 <$Lang::tr{'adrschk location'} +END + + if ($location_settings{'LOCATIONBLOCK_ENABLED'} ne 'on') + { + print "

$Lang::tr{'adrschk location disabled'}

\n"; + } + + print < + +   + + $Lang::tr{'country'} + + ASN + $Lang::tr{'adrschk blocked'} + +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 = "-"; + } + elsif ($location_settings{$ccode} eq 'on') + { + $blocked = "$Lang::tr{'adrschk blocked'}"; + } + else + { + $blocked = "$Lang::tr{'adrschk not blocked'}"; + } + + print < + $address + $ccode + $cname + $asn + $asnname + $blocked + +END + } + + print <
+END +} + + +#------------------------------------------------------------------------------ +# sub error() +# +# Shows error messages +#------------------------------------------------------------------------------ + +sub error() +{ + Header::openbox('100%', 'left', $Lang::tr{'error messages'}); + print "$errormessage\n"; + print " \n"; + Header::closebox(); +} diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl index 41723f19a..db4096b06 100644 --- a/langs/en/cgi-bin/en.pl +++ b/langs/en/cgi-bin/en.pl @@ -7,6 +7,25 @@ 'Add Port Rule' => 'Add port rule', 'Add Rule' => 'Add rule', 'Add a route' => 'Add a route', +'adrschk address check' => 'Address check', +'adrschk address' => 'Address', +'adrschk address or url' => 'IP address or URL', +'adrschk check' => 'Check', +'adrschk invalid check address' => 'Address to be checked is not a valid URL or IP address', +'adrschk check result' => 'Result of check on ', +'adrschk dns lookup' => 'DNS Lookup', +'adrschk authenticated' => 'Authenticated', +'adrschk signed' => 'Signed', +'adrschk url not found' => 'Domain not found on servers', +'adrschk no dns servers' => 'No DNS servers found', +'adrschk url filter' => 'URL Filter', +'adrschk url filter disabled' => 'URL Filter is not enabled', +'adrschk blocked' => 'Blocked', +'adrschk not blocked' => 'Not blocked', +'adrschk ipblacklist' => 'IP Address Blacklist', +'adrschk ipblacklists disabled' => 'IP Address Blacklists are not enabled', +'adrschk location' => 'Location', +'adrschk location disabled' => 'Location based blocking not enabled', 'Async logging enabled' => 'Enable asynchronous writing of the syslog file', 'Captive' => 'Captive Portal', 'Captive 1day' => '1 day',