#!/usr/bin/perl
###############################################################################
# #
# IPFire.org - A linux based firewall #
# Copyright (C) 2007 Michael Tremer & Christian Schmidt #
# #
# 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 . #
# #
###############################################################################
use strict;
# enable only the following on debugging purpose
#use warnings;
#use CGI::Carp 'fatalsToBrowser';
require '/var/ipfire/general-functions.pl';
require "${General::swroot}/lang.pl";
require "${General::swroot}/header.pl";
#workaround to suppress a warning when a variable is used only once
my @dummy = ( ${Header::colouryellow} );
undef (@dummy);
my %color = ();
my %mainsettings = ();
&General::readhash("${General::swroot}/main/settings", \%mainsettings);
&General::readhash("/srv/web/ipfire/html/themes/".$mainsettings{'THEME'}."/include/colors.txt", \%color);
my %cgiparams=();
my %selected=();
my %checked=();
my $prtrange1=0;
my $prtrange2=0;
my $errormessage = '';
my $filename = "${General::swroot}/portfw/config";
my $aliasfile = "${General::swroot}/ethernet/aliases";
&Header::showhttpheaders();
$cgiparams{'ENABLED'} = 'off';
$cgiparams{'KEY1'} = '0';
$cgiparams{'KEY2'} = '0';
$cgiparams{'PROTOCOL'} = '';
$cgiparams{'SRC_PORT'} = '';
$cgiparams{'DEST_IP'} = '';
$cgiparams{'DEST_PORT'} = '';
$cgiparams{'SRC_IP'} = '';
$cgiparams{'ORIG_IP'} = '';
$cgiparams{'REMARK'} = '';
$cgiparams{'OVERRIDE'} = 'off';
$cgiparams{'ACTION'} = '';
&Header::getcgihash(\%cgiparams);
my $disable_all = "0";
my $enable_all = "0";
if ($cgiparams{'ACTION'} eq $Lang::tr{'add'})
{
&valaddupdate();
# Darren Critchley - if there is an error, don't waste any more time processing
if ($errormessage) { goto ERROR; }
open(FILE, $filename) or die 'Unable to open config file.';
my @current = ;
close(FILE);
my $key1 = 0; # used for finding last sequence number used
foreach my $line (@current)
{
my @temp = split(/\,/,$line);
chomp ($temp[8]);
if ($cgiparams{'KEY2'} eq "0"){ # if key2 is 0 then it is a portfw addition
if ( $cgiparams{'SRC_PORT'} eq $temp[3] &&
$cgiparams{'PROTOCOL'} eq $temp[2] &&
$cgiparams{'SRC_IP'} eq $temp[7])
{
$errormessage =
"$Lang::tr{'source port in use'} $cgiparams{'SRC_PORT'}";
}
# Check if key2 = 0, if it is then it is a port forward entry and we want the sequence number
if ( $temp[1] eq "0") {
$key1=$temp[0];
}
# Darren Critchley - Duplicate or overlapping Port range check
if ($temp[1] eq "0" &&
$cgiparams{'PROTOCOL'} eq $temp[2] &&
$cgiparams{'SRC_IP'} eq $temp[7] &&
$errormessage eq '')
{
&portchecks($temp[3], $temp[5]);
}
} else {
if ( $cgiparams{'KEY1'} eq $temp[0] &&
$cgiparams{'ORIG_IP'} eq $temp[8])
{
$errormessage =
"$Lang::tr{'source ip in use'} $cgiparams{'ORIG_IP'}";
}
}
}
ERROR:
unless ($errormessage)
{
# Darren Critchley - we only want to store ranges with Colons
$cgiparams{'SRC_PORT'} =~ tr/-/:/;
$cgiparams{'DEST_PORT'} =~ tr/-/:/;
if ($cgiparams{'KEY1'} eq "0") { # 0 in KEY1 indicates it is a portfw add
$key1++; # Add one to last sequence number
open(FILE,">>$filename") or die 'Unable to open config file.';
flock FILE, 2;
if ($cgiparams{'ORIG_IP'} eq '0.0.0.0/0') {
# if the default/all is taken, then write it to the rule
print FILE "$key1,0,$cgiparams{'PROTOCOL'},$cgiparams{'SRC_PORT'},$cgiparams{'DEST_IP'},$cgiparams{'DEST_PORT'},$cgiparams{'ENABLED'},$cgiparams{'SRC_IP'},$cgiparams{'ORIG_IP'},$cgiparams{'REMARK'}\n";
} else { # else create an extra record so it shows up
print FILE "$key1,0,$cgiparams{'PROTOCOL'},$cgiparams{'SRC_PORT'},$cgiparams{'DEST_IP'},$cgiparams{'DEST_PORT'},$cgiparams{'ENABLED'},$cgiparams{'SRC_IP'},0,$cgiparams{'REMARK'}\n";
print FILE "$key1,1,$cgiparams{'PROTOCOL'},0,$cgiparams{'DEST_IP'},$cgiparams{'DEST_PORT'},$cgiparams{'ENABLED'},0,$cgiparams{'ORIG_IP'},$cgiparams{'REMARK'}\n";
}
close(FILE);
undef %cgiparams;
&General::log($Lang::tr{'forwarding rule added'});
system('/usr/local/bin/setportfw');
} else { # else key1 eq 0
my $insertpoint = ($cgiparams{'KEY2'} - 1);
open(FILE, ">$filename") or die 'Unable to open config file.';
flock FILE, 2;
foreach my $line (@current) {
chomp($line);
my @temp = split(/\,/,$line);
if ($cgiparams{'KEY1'} eq $temp[0] && $insertpoint eq $temp[1]) {
if ($temp[1] eq "0") { # this is the first xtaccess rule, therefore modify the portfw rule
$temp[8] = '0';
}
print FILE "$temp[0],$temp[1],$temp[2],$temp[3],$temp[4],$temp[5],$temp[6],$temp[7],$temp[8],$temp[9]\n";
print FILE "$cgiparams{'KEY1'},$cgiparams{'KEY2'},$cgiparams{'PROTOCOL'},0,$cgiparams{'DEST_IP'},$cgiparams{'DEST_PORT'},$cgiparams{'ENABLED'},0,$cgiparams{'ORIG_IP'},$cgiparams{'REMARK'}\n";
} else {
print FILE "$line\n";
}
}
close(FILE);
undef %cgiparams;
&General::log($Lang::tr{'external access rule added'});
system('/usr/local/bin/setportfw');
} # end if if KEY1 eq 0
} # end unless($errormessage)
}
if ($cgiparams{'ACTION'} eq $Lang::tr{'update'})
{
&valaddupdate();
# Darren Critchley - If there is an error don't waste any more processing time
if ($errormessage) { $cgiparams{'ACTION'} = $Lang::tr{'edit'}; goto UPD_ERROR; }
open(FILE, $filename) or die 'Unable to open config file.';
my @current = ;
close(FILE);
my $disabledpfw = '0';
my $lastpfw = '';
my $xtaccessdel = '0';
foreach my $line (@current)
{
my @temp = split(/\,/,$line);
if ( $temp[1] eq "0" ) { # keep track of the last portfw and if it is enabled
$disabledpfw = $temp[6];
$lastpfw = $temp[0];
}
chomp ($temp[8]);
if ( $cgiparams{'SRC_PORT'} eq $temp[3] &&
$cgiparams{'PROTOCOL'} eq $temp[2] &&
$cgiparams{'SRC_IP'} eq $temp[7])
{
if ($cgiparams{'KEY1'} ne $temp[0] && $cgiparams{'KEY2'} eq "0")
{
$errormessage =
"$Lang::tr{'source port in use'} $cgiparams{'SRC_PORT'}";
}
}
if ($cgiparams{'ORIG_IP'} eq $temp[8])
{
if ($cgiparams{'KEY1'} eq $temp[0] && $cgiparams{'KEY2'} ne $temp[1])
# If we have the same source ip within a portfw group, then we have a problem!
{
$errormessage = "$Lang::tr{'source ip in use'} $cgiparams{'ORIG_IP'}";
$cgiparams{'ACTION'} = $Lang::tr{'edit'};
}
}
# Darren Critchley - Flag when a user disables an xtaccess
if ($cgiparams{'KEY1'} eq $temp[0] &&
$cgiparams{'KEY2'} eq $temp[1] &&
$cgiparams{'KEY2'} ne "0" && # if KEY2 is 0 then it is a portfw
$cgiparams{'ENABLED'} eq "off" &&
$temp[6] eq "on") { # we have determined that someone has turned an xtaccess off
$xtaccessdel = "1";
}
# Darren Critchley - Portfw enabled, then enable xtaccess for all associated xtaccess records
if ($cgiparams{'ENABLED'} eq "on" && $cgiparams{'KEY2'} eq "0" && $cgiparams{'ENABLED'} ne $temp[6])
{
$enable_all = "1";
} else {
$enable_all = "0";
}
# Darren Critchley - Portfw disabled, then disable xtaccess for all associated xtaccess records
if ($cgiparams{'ENABLED'} eq "off" && $cgiparams{'KEY2'} eq "0")
{
$disable_all = "1";
} else {
$disable_all = "0";
}
# Darren Critchley - if we are enabling an xtaccess, only allow if the associated Portfw is enabled
if ($cgiparams{'KEY1'} eq $lastpfw && $cgiparams{'KEY2'} ne "0") { # identifies an xtaccess record in the group
if ($cgiparams{'ENABLED'} eq "on" && $cgiparams{'ENABLED'} ne $temp[6] ){ # a change has been made
if ($disabledpfw eq "off")
{
$errormessage = "$Lang::tr{'cant enable xtaccess'}";
$cgiparams{'ACTION'} = $Lang::tr{'edit'};
}
}
}
# Darren Critchley - rule to stop someone from entering ALL into a external access rule,
# the portfw is the only place that ALL can be specified
if ($cgiparams{'KEY2'} ne "0" && $cgiparams{'ORIG_IP'} eq "0.0.0.0/0") {
$errormessage = "$Lang::tr{'xtaccess all error'}";
$cgiparams{'ACTION'} = $Lang::tr{'edit'};
}
# Darren Critchley - Duplicate or overlapping Port range check
if ($temp[1] eq "0" &&
$cgiparams{'KEY1'} ne $temp[0] &&
$cgiparams{'PROTOCOL'} eq $temp[2] &&
$cgiparams{'SRC_IP'} eq $temp[7] &&
$errormessage eq '')
{
&portchecks($temp[3], $temp[5]);
} # end port testing
}
# Darren Critchley - if an xtaccess was disabled, now we need to check to see if it was the only xtaccess
if($xtaccessdel eq "1") {
my $xctr = 0;
foreach my $line (@current)
{
my @temp = split(/\,/,$line);
if($temp[0] eq $cgiparams{'KEY1'} &&
$temp[6] eq "on") { # we only want to count the enabled xtaccess's
$xctr++;
}
}
if ($xctr == 2){
$disable_all = "1";
}
}
UPD_ERROR:
unless ($errormessage)
{
# Darren Critchley - we only want to store ranges with Colons
$cgiparams{'SRC_PORT'} =~ tr/-/:/;
$cgiparams{'DEST_PORT'} =~ tr/-/:/;
open(FILE, ">$filename") or die 'Unable to open config file.';
flock FILE, 2;
foreach my $line (@current) {
chomp($line);
my @temp = split(/\,/,$line);
if ($cgiparams{'KEY1'} eq $temp[0] && $cgiparams{'KEY2'} eq $temp[1]) {
print FILE "$cgiparams{'KEY1'},$cgiparams{'KEY2'},$cgiparams{'PROTOCOL'},$cgiparams{'SRC_PORT'},$cgiparams{'DEST_IP'},$cgiparams{'DEST_PORT'},$cgiparams{'ENABLED'},$cgiparams{'SRC_IP'},$cgiparams{'ORIG_IP'},$cgiparams{'REMARK'}\n";
} else {
# Darren Critchley - If it is a port forward record, then chances are good that a change was made to
# Destination Ip or Port, and we need to update all the associated external access records
if ($cgiparams{'KEY2'} eq "0" && $cgiparams{'KEY1'} eq $temp[0]) {
$temp[4] = $cgiparams{'DEST_IP'};
$temp[5] = $cgiparams{'DEST_PORT'};
$temp[2] = $cgiparams{'PROTOCOL'};
}
# Darren Critchley - If a Portfw has been disabled, then set all associated xtaccess as disabled
if ( $disable_all eq "1" && $cgiparams{'KEY1'} eq $temp[0] ) {
$temp[6] = 'off';
}
if ( $enable_all eq "1" && $cgiparams{'KEY1'} eq $temp[0] ) {
$temp[6] = 'on';
}
# Darren Critchley - Deal with the override to allow ALL
if ( $cgiparams{'OVERRIDE'} eq "on" && $temp[1] ne "0" && $cgiparams{'KEY1'} eq $temp[0] ) {
$temp[6] = 'off';
}
print FILE "$temp[0],$temp[1],$temp[2],$temp[3],$temp[4],$temp[5],$temp[6],$temp[7],$temp[8],$temp[9]\n";
}
}
close(FILE);
undef %cgiparams;
&General::log($Lang::tr{'forwarding rule updated'});
system('/usr/local/bin/setportfw');
}
if ($errormessage) {
$cgiparams{'ACTION'} = $Lang::tr{'edit'};
}
}
# Darren Critchley - Allows rules to be enabled and disabled
if ($cgiparams{'ACTION'} eq $Lang::tr{'toggle enable disable'})
{
open(FILE, $filename) or die 'Unable to open config file.';
my @current = ;
close(FILE);
my $disabledpfw = '0';
my $lastpfw = '';
my $xtaccessdel = '0';
foreach my $line (@current)
{
my @temp = split(/\,/,$line);
if ( $temp[1] eq "0" ) { # keep track of the last portfw and if it is enabled
$disabledpfw = $temp[6];
$lastpfw = $temp[0];
}
# Darren Critchley - Flag when a user disables an xtaccess
if ($cgiparams{'KEY1'} eq $temp[0] &&
$cgiparams{'KEY2'} eq $temp[1] &&
$cgiparams{'KEY2'} ne "0" && # if KEY2 is 0 then it is a portfw
$cgiparams{'ENABLED'} eq "off" &&
$temp[6] eq "on") { # we have determined that someone has turned an xtaccess off
$xtaccessdel = "1";
}
# Darren Critchley - Portfw enabled, then enable xtaccess for all associated xtaccess records
if ($cgiparams{'ENABLED'} eq "on" && $cgiparams{'KEY2'} eq "0" && $cgiparams{'ENABLED'} ne $temp[6])
{
$enable_all = "1";
} else {
$enable_all = "0";
}
# Darren Critchley - Portfw disabled, then disable xtaccess for all associated xtaccess records
if ($cgiparams{'ENABLED'} eq "off" && $cgiparams{'KEY2'} eq "0")
{
$disable_all = "1";
} else {
$disable_all = "0";
}
# Darren Critchley - if we are enabling an xtaccess, only allow if the associated Portfw is enabled
if ($cgiparams{'KEY1'} eq $lastpfw && $cgiparams{'KEY2'} ne "0") { # identifies an xtaccess record in the group
if ($cgiparams{'ENABLED'} eq "on" && $cgiparams{'ENABLED'} ne $temp[6] ){ # a change has been made
if ($disabledpfw eq "off")
{
$errormessage = "$Lang::tr{'cant enable xtaccess'}";
goto TOGGLEEXIT;
}
}
}
}
# Darren Critchley - if an xtaccess was disabled, now we need to check to see if it was the only xtaccess
if($xtaccessdel eq "1") {
my $xctr = 0;
foreach my $line (@current)
{
my @temp = split(/\,/,$line);
if($temp[0] eq $cgiparams{'KEY1'} &&
$temp[6] eq "on") { # we only want to count the enabled xtaccess's
$xctr++;
}
}
if ($xctr == 2){
$disable_all = "1";
}
}
open(FILE, ">$filename") or die 'Unable to open config file.';
flock FILE, 2;
foreach my $line (@current) {
chomp($line);
my @temp = split(/\,/,$line);
if ($cgiparams{'KEY1'} eq $temp[0] && $cgiparams{'KEY2'} eq $temp[1]) {
print FILE "$cgiparams{'KEY1'},$cgiparams{'KEY2'},$temp[2],$temp[3],$temp[4],$temp[5],$cgiparams{'ENABLED'},$temp[7],$temp[8],$temp[9]\n";
} else {
# Darren Critchley - If a Portfw has been disabled, then set all associated xtaccess as disabled
if ( $disable_all eq "1" && $cgiparams{'KEY1'} eq $temp[0] ) {
$temp[6] = 'off';
}
if ( $enable_all eq "1" && $cgiparams{'KEY1'} eq $temp[0] ) {
$temp[6] = 'on';
}
print FILE "$temp[0],$temp[1],$temp[2],$temp[3],$temp[4],$temp[5],$temp[6],$temp[7],$temp[8],$temp[9]\n";
}
}
close(FILE);
&General::log($Lang::tr{'forwarding rule updated'});
system('/usr/local/bin/setportfw');
TOGGLEEXIT:
undef %cgiparams;
}
# Darren Critchley - broke out Edit routine from the delete routine - Edit routine now just puts values in fields
if ($cgiparams{'ACTION'} eq $Lang::tr{'edit'})
{
open(FILE, "$filename") or die 'Unable to open config file.';
my @current = ;
close(FILE);
unless ($errormessage)
{
foreach my $line (@current)
{
chomp($line);
my @temp = split(/\,/,$line);
if ($cgiparams{'KEY1'} eq $temp[0] && $cgiparams{'KEY2'} eq $temp[1] ) {
$cgiparams{'PROTOCOL'} = $temp[2];
$cgiparams{'SRC_PORT'} = $temp[3];
$cgiparams{'DEST_IP'} = $temp[4];
$cgiparams{'DEST_PORT'} = $temp[5];
$cgiparams{'ENABLED'} = $temp[6];
$cgiparams{'SRC_IP'} = $temp[7];
$cgiparams{'ORIG_IP'} = $temp[8];
$cgiparams{'REMARK'} = $temp[9];
}
}
}
}
# Darren Critchley - broke out Remove routine as the logic is getting too complex to be combined with the Edit
if ($cgiparams{'ACTION'} eq $Lang::tr{'remove'})
{
open(FILE, "$filename") or die 'Unable to open config file.';
my @current = ;
close(FILE);
# If the record being deleted is an xtaccess record, and it is the only one for a portfw record
# then we need to adjust the portfw record to be open to ALL ip addressess or an error will occur
# in setportfw.c
my $fixportfw = '0';
if ($cgiparams{'KEY2'} ne "0") {
my $counter = 0;
foreach my $line (@current)
{
chomp($line);
my @temp = split(/\,/,$line);
if ($temp[0] eq $cgiparams{'KEY1'}) {
$counter++;
}
}
if ($counter eq 2) {
$fixportfw = '1';
}
}
unless ($errormessage)
{
open(FILE, ">$filename") or die 'Unable to open config file.';
flock FILE, 2;
my $linedeleted = 0;
foreach my $line (@current)
{
chomp($line);
my @temp = split(/\,/,$line);
if ($cgiparams{'KEY1'} eq $temp[0] && $cgiparams{'KEY2'} eq $temp[1] ||
$cgiparams{'KEY1'} eq $temp[0] && $cgiparams{'KEY2'} eq "0" )
{
$linedeleted = 1;
} else {
if ($temp[0] eq $cgiparams{'KEY1'} && $temp[1] eq "0" && $fixportfw eq "1") {
$temp[8] = '0.0.0.0/0';
}
print FILE "$temp[0],$temp[1],$temp[2],$temp[3],$temp[4],$temp[5],$temp[6],$temp[7],$temp[8],$temp[9]\n";
# print FILE "$line\n";
}
}
close(FILE);
if ($linedeleted == 1) {
&General::log($Lang::tr{'forwarding rule removed'});
undef %cgiparams;
}
system('/usr/local/bin/setportfw');
}
}
# Darren Critchley - Added routine to allow external access rules to be added
if ($cgiparams{'ACTION'} eq $Lang::tr{'add xtaccess'})
{
open(FILE, $filename) or die 'Unable to open config file.';
my @current = ;
close(FILE);
my $key = 0; # used for finding last sequence number used
foreach my $line (@current)
{
my @temp = split(/\,/,$line);
if ($temp[0] eq $cgiparams{'KEY1'}) {
$key = $temp[1]
}
if ($cgiparams{'KEY1'} eq $temp[0] && $cgiparams{'KEY2'} eq $temp[1] ) {
$cgiparams{'PROTOCOL'} = $temp[2];
$cgiparams{'SRC_PORT'} = $temp[3];
$cgiparams{'DEST_IP'} = $temp[4];
$cgiparams{'DEST_PORT'} = $temp[5];
$cgiparams{'ENABLED'} = $temp[6];
$cgiparams{'SRC_IP'} = $temp[7];
$cgiparams{'ORIG_IP'} = '';
$cgiparams{'REMARK'} = $temp[9];
}
}
$key++;
$cgiparams{'KEY2'} = $key;
# Until the ADD button is hit, there needs to be no change to portfw rules
}
if ($cgiparams{'ACTION'} eq $Lang::tr{'reset'})
{
undef %cgiparams;
}
if ($cgiparams{'ACTION'} eq '')
{
$cgiparams{'PROTOCOL'} = 'tcp';
$cgiparams{'ENABLED'} = 'on';
$cgiparams{'SRC_IP'} = '0.0.0.0';
}
$selected{'PROTOCOL'}{'udp'} = '';
$selected{'PROTOCOL'}{'tcp'} = '';
$selected{'PROTOCOL'}{'gre'} = '';
$selected{'PROTOCOL'}{$cgiparams{'PROTOCOL'}} = "selected='selected'";
$selected{'SRC_IP'}{$cgiparams{'SRC_IP'}} = "selected='selected'";
$checked{'ENABLED'}{'off'} = '';
$checked{'ENABLED'}{'on'} = '';
$checked{'ENABLED'}{$cgiparams{'ENABLED'}} = "checked='checked'";
&Header::openpage($Lang::tr{'port forwarding configuration'}, 1, '');
&Header::openbigbox('100%', 'left', '', $errormessage);
if ($errormessage) {
&Header::openbox('100%', 'left', $Lang::tr{'error messages'});
print "$errormessage\n";
print " \n";
&Header::closebox();
}
print "