2007-03-15 22:55:22 +01:00
#
2007-07-26 20:36:18 +02:00
# Shorewall-perl 4.0 -- /usr/share/shorewall-perl/Shorewall/Zones.pm
2007-03-15 22:55:22 +01:00
#
2007-09-08 18:09:51 +02:00
# This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]
2007-03-15 22:55:22 +01:00
#
# (c) 2007 - Tom Eastep (teastep@shorewall.net)
#
# Complete documentation is available at http://shorewall.net
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of Version 2 of the GNU General Public License
# as published by the Free Software Foundation.
#
# 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, write to the Free Software
2007-09-08 18:09:51 +02:00
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2007-03-15 22:55:22 +01:00
#
2007-07-26 20:36:18 +02:00
# This module contains the code which deals with /etc/shorewall/zones,
2007-07-17 00:07:50 +02:00
# /etc/shorewall/interfaces and /etc/shorewall/hosts.
2007-03-15 22:55:22 +01:00
#
2007-03-14 04:19:25 +01:00
package Shorewall::Zones ;
require Exporter ;
use Shorewall::Config ;
2007-07-17 00:07:50 +02:00
use Shorewall::IPAddrs ;
2007-03-14 04:19:25 +01:00
2007-03-15 01:34:17 +01:00
use strict ;
2007-03-14 04:19:25 +01:00
our @ ISA = qw( Exporter ) ;
2007-03-15 00:09:05 +01:00
our @ EXPORT = qw( NOTHING
NUMERIC
NETWORK
IPSECPROTO
IPSECMODE
2007-03-27 01:17:46 +02:00
2007-08-26 17:12:04 +02:00
numeric_value
2007-03-15 00:09:05 +01:00
determine_zones
zone_report
2007-03-21 21:35:40 +01:00
dump_zone_contents
2007-09-10 17:52:57 +02:00
find_zone
firewall_zone
defined_zone
zone_type
all_zones
complex_zones
non_firewall_zones
2007-06-07 04:21:54 +02:00
single_interface
2007-07-17 00:07:50 +02:00
validate_interfaces_file
2007-09-10 17:52:57 +02:00
all_interfaces
find_interface
2007-07-17 00:07:50 +02:00
known_interface
2007-09-10 17:52:57 +02:00
have_bridges
2007-07-17 00:07:50 +02:00
port_to_bridge
source_port_to_bridge
interface_is_optional
find_interfaces_by_option
get_interface_option
validate_hosts_file
find_hosts_by_option
2007-09-10 17:52:57 +02:00
) ;
2007-03-15 01:34:17 +01:00
2007-06-14 01:02:39 +02:00
our @ EXPORT_OK = qw( initialize ) ;
2007-09-21 18:55:28 +02:00
our $ VERSION = '4.04' ;
2007-03-14 04:19:25 +01:00
2007-03-15 00:09:05 +01:00
#
# IPSEC Option types
#
use constant { NOTHING = > 'NOTHING' ,
NUMERIC = > '0x[\da-fA-F]+|\d+' ,
NETWORK = > '\d+.\d+.\d+.\d+(\/\d+)?' ,
IPSECPROTO = > 'ah|esp|ipcomp' ,
IPSECMODE = > 'tunnel|transport'
} ;
2007-03-14 04:19:25 +01:00
#
2007-04-08 16:42:26 +02:00
# Zone Table.
2007-03-14 04:19:25 +01:00
#
# @zones contains the ordered list of zones with sub-zones appearing before their parents.
#
2007-09-12 17:03:47 +02:00
# %zones{<zone1> => {type = > <zone type> 'firewall', 'ipv4', 'ipsec4', 'bport4';
2007-03-14 04:19:25 +01:00
# options => { complex => 0|1
# in_out => < policy match string >
# in => < policy match string >
2007-04-08 16:42:26 +02:00
# out => < policy match string >
2007-03-14 04:19:25 +01:00
# }
# parents => [ <parents> ] Parents, Children and interfaces are listed by name
# children => [ <children> ]
# interfaces => [ <interfaces> ]
2007-06-06 02:47:27 +02:00
# bridge => <bridge>
2007-03-14 04:19:25 +01:00
# hosts { <type> } => [ { <interface1> => { ipsec => 'ipsec'|'none'
# options => { <option1> => <value1>
# ...
# }
# hosts => [ <net1> , <net2> , ... ]
# }
# <interface2> => ...
# }
# ]
# }
# <zone2> => ...
# }
#
# $firewall_zone names the firewall zone.
#
2007-04-08 16:42:26 +02:00
our @ zones ;
2007-03-14 05:29:14 +01:00
our % zones ;
our $ firewall_zone ;
2007-03-14 04:19:25 +01:00
2007-06-05 18:49:13 +02:00
our % reservedName = ( all = > 1 ,
none = > 1 ,
SOURCE = > 1 ,
DEST = > 1 ) ;
2007-04-20 16:13:57 +02:00
2007-07-17 00:07:50 +02:00
#
# Interface Table.
#
# @interfaces lists the interface names in the order that they appear in the interfaces file.
#
# %interfaces { <interface1> => { root => <name without trailing '+'>
# options => { <option1> = <val1> ,
# ...
# }
2007-09-11 19:29:41 +02:00
# zone => <zone name>
2007-07-17 00:07:50 +02:00
# bridge => <bridge>
2007-08-10 19:37:02 +02:00
# broadcasts => 'none', 'detect' or [ <addr1>, <addr2>, ... ]
2007-07-17 00:07:50 +02:00
# }
# }
#
our @ interfaces ;
our % interfaces ;
2007-09-21 18:55:28 +02:00
our @ bport_zones ;
2007-07-17 00:07:50 +02:00
2007-06-15 00:07:45 +02:00
#
# Initialize globals -- we take this novel approach to globals initialization to allow
# the compiler to run multiple times in the same process. The
# initialize() function does globals initialization for this
# module and is called from an INIT block below. The function is
# also called by Shorewall::Compiler::compiler at the beginning of
2007-07-26 20:36:18 +02:00
# the second and subsequent calls to that function.
2007-06-15 00:07:45 +02:00
#
2007-06-14 01:02:39 +02:00
sub initialize () {
@ zones = ( ) ;
% zones = ( ) ;
2007-06-19 01:04:17 +02:00
$ firewall_zone = '' ;
2007-07-17 00:07:50 +02:00
@ interfaces = ( ) ;
% interfaces = ( ) ;
2007-09-21 18:55:28 +02:00
@ bport_zones = ( ) ;
2007-06-14 01:02:39 +02:00
}
INIT {
initialize ;
}
2007-08-26 17:12:04 +02:00
#
# Convert value to decimal number
#
sub numeric_value ( $ ) {
my $ mark = $ _ [ 0 ] ;
fatal_error "Invalid Numeric Value ($mark)" unless "\L$mark" =~ /^(0x[a-f0-9]+|0[0-7]*|[1-9]\d*)$/ ;
$ mark =~ /^0/ ? oct $ mark : $ mark ;
}
2007-03-14 04:19:25 +01:00
#
# Parse the passed option list and return a reference to a hash as follows:
#
# => mss = <MSS setting>
# => ipsec = <-m policy arguments to match options>
#
2007-04-22 17:41:08 +02:00
sub parse_zone_option_list ($$)
2007-03-14 04:19:25 +01:00
{
my % validoptions = ( mss = > NUMERIC ,
2007-04-18 22:53:25 +02:00
strict = > NOTHING ,
next = > NOTHING ,
reqid = > NUMERIC ,
spi = > NUMERIC ,
proto = > IPSECPROTO ,
mode = > IPSECMODE ,
"tunnel-src" = > NETWORK ,
"tunnel-dst" = > NETWORK ,
2007-03-14 04:19:25 +01:00
) ;
#
# Hash of options that have their own key in the returned hash.
#
my % key = ( mss = > "mss" ) ;
2007-04-22 17:41:08 +02:00
my ( $ list , $ zonetype ) = @ _ ;
2007-03-14 04:19:25 +01:00
my % h ;
my $ options = '' ;
my $ fmt ;
if ( $ list ne '-' ) {
for my $ e ( split ',' , $ list ) {
my $ val = undef ;
my $ invert = '' ;
if ( $ e =~ /([\w-]+)!=(.+)/ ) {
$ val = $ 2 ;
$ e = $ 1 ;
$ invert = '! ' ;
} elsif ( $ e =~ /([\w-]+)=(.+)/ ) {
$ val = $ 2 ;
$ e = $ 1 ;
}
2007-03-27 01:17:46 +02:00
2007-03-14 04:19:25 +01:00
$ fmt = $ validoptions { $ e } ;
fatal_error "Invalid Option ($e)" unless $ fmt ;
2007-03-27 01:17:46 +02:00
2007-03-14 04:19:25 +01:00
if ( $ fmt eq NOTHING ) {
2007-04-22 16:29:30 +02:00
fatal_error "Option \"$e\" does not take a value" if defined $ val ;
2007-03-14 04:19:25 +01:00
} else {
2007-04-22 16:29:30 +02:00
fatal_error "Missing value for option \"$e\"" unless defined $ val ;
2007-03-30 04:05:11 +02:00
fatal_error "Invalid value ($val) for option \"$e\"" unless $ val =~ /^($fmt)$/ ;
2007-03-14 04:19:25 +01:00
}
2007-03-27 01:17:46 +02:00
2007-03-14 04:19:25 +01:00
if ( $ key { $ e } ) {
$ h { $ e } = $ val ;
} else {
2007-09-12 17:03:47 +02:00
fatal_error "The \"$e\" option may only be specified for ipsec zones" unless $ zonetype eq 'ipsec4' ;
2007-03-14 04:19:25 +01:00
$ options . = $ invert ;
$ options . = "--$e " ;
$ options . = "$val " if defined $ val ;
}
}
}
$ h { ipsec } = $ options ? "$options " : '' ;
2007-04-08 16:42:26 +02:00
\ % h ;
2007-03-14 04:19:25 +01:00
}
#
# Parse the zones file.
2007-03-27 01:17:46 +02:00
#
2007-03-14 04:19:25 +01:00
sub determine_zones ()
{
my @ z ;
2007-03-30 17:57:08 +02:00
my $ fn = open_file 'zones' ;
my $ first_entry = 1 ;
2007-03-14 04:19:25 +01:00
2007-03-29 17:47:47 +02:00
while ( read_a_line ) {
2007-03-27 01:17:46 +02:00
2007-03-30 17:57:08 +02:00
if ( $ first_entry ) {
2007-04-08 16:42:26 +02:00
progress_message2 "$doing $fn..." ;
2007-03-30 17:57:08 +02:00
$ first_entry = 0 ;
}
2007-05-09 21:05:27 +02:00
my @ parents ;
my ( $ zone , $ type , $ options , $ in_options , $ out_options ) = split_line 1 , 5 , 'zones file' ;
2007-03-14 04:19:25 +01:00
if ( $ zone =~ /(\w+):([\w,]+)/ ) {
$ zone = $ 1 ;
@ parents = split ',' , $ 2 ;
for my $ p ( @ parents ) {
fatal_error "Invalid Parent List ($2)" unless $ p ;
fatal_error "Unknown parent zone ($p)" unless $ zones { $ p } ;
2007-09-12 17:03:47 +02:00
fatal_error 'Subzones of firewall zone not allowed' if $ zones { $ p } { type } eq 'firewall' ;
2007-03-14 04:19:25 +01:00
push @ { $ zones { $ p } { children } } , $ zone ;
}
}
2007-06-16 23:08:12 +02:00
fatal_error "Invalid zone name ($zone)" unless "\L$zone" =~ /^[a-z]\w*$/ && length $ zone <= $ globals { MAXZONENAMELENGTH } ;
fatal_error "Invalid zone name ($zone)" if $ reservedName { $ zone } || $ zone =~ /^all2|2all$/ ;
fatal_error ( "Duplicate zone name ($zone)" ) if $ zones { $ zone } ;
2007-03-14 04:19:25 +01:00
$ type = "ipv4" unless $ type ;
if ( $ type =~ /ipv4/i ) {
2007-09-12 17:03:47 +02:00
$ type = 'ipv4' ;
2007-03-14 04:19:25 +01:00
} elsif ( $ type =~ /^ipsec4?$/i ) {
2007-09-12 17:03:47 +02:00
$ type = 'ipsec4' ;
2007-06-06 02:47:27 +02:00
} elsif ( $ type =~ /^bport4?$/i ) {
2007-06-06 22:06:16 +02:00
warning_message "Bridge Port zones should have a parent zone" unless @ parents ;
2007-09-12 17:03:47 +02:00
$ type = 'bport4' ;
2007-09-21 18:55:28 +02:00
push @ bport_zones , $ zone ;
2007-03-14 04:19:25 +01:00
} elsif ( $ type eq 'firewall' ) {
fatal_error 'Firewall zone may not be nested' if @ parents ;
2007-06-16 23:08:12 +02:00
fatal_error "Only one firewall zone may be defined ($zone)" if $ firewall_zone ;
2007-03-14 04:19:25 +01:00
$ firewall_zone = $ zone ;
2007-04-01 21:48:20 +02:00
$ ENV { FW } = $ zone ;
2007-09-12 17:03:47 +02:00
$ type = "firewall" ;
2007-03-14 04:19:25 +01:00
} elsif ( $ type eq '-' ) {
2007-09-12 17:03:47 +02:00
$ type = 'ipv4' ;
2007-03-14 04:19:25 +01:00
} else {
fatal_error "Invalid zone type ($type)" ;
}
2007-06-23 18:06:16 +02:00
for ( $ options , $ in_options , $ out_options ) {
$ _ = '' if $ _ eq '-' ;
}
2007-06-07 01:39:27 +02:00
$ zones { $ zone } = { type = > $ type ,
2007-07-26 20:36:18 +02:00
parents = > \ @ parents ,
exclusions = > [] ,
2007-06-07 01:39:27 +02:00
bridge = > '' ,
2007-06-23 18:06:16 +02:00
options = > { in_out = > parse_zone_option_list ( $ options || '' , $ type ) ,
in = > parse_zone_option_list ( $ in_options || '' , $ type ) ,
out = > parse_zone_option_list ( $ out_options || '' , $ type ) ,
2007-09-12 17:03:47 +02:00
complex = > ( $ type eq 'ipsec4' || $ options || $ in_options || $ out_options ? 1 : 0 ) } ,
2007-06-07 01:39:27 +02:00
interfaces = > { } ,
children = > [] ,
hosts = > { }
} ;
2007-03-14 04:19:25 +01:00
push @ z , $ zone ;
}
2007-04-20 16:58:11 +02:00
fatal_error "No firewall zone defined" unless $ firewall_zone ;
2007-07-26 20:36:18 +02:00
2007-03-14 04:19:25 +01:00
my % ordered ;
2007-09-10 17:52:57 +02:00
PUSHED:
2007-03-14 04:19:25 +01:00
{
ZONE:
for my $ zone ( @ z ) {
unless ( $ ordered { $ zone } ) {
2007-09-10 17:52:57 +02:00
for ( @ { $ zones { $ zone } { children } } ) {
next ZONE unless $ ordered { $ _ } ;
2007-03-14 04:19:25 +01:00
}
$ ordered { $ zone } = 1 ;
push @ zones , $ zone ;
2007-09-10 17:52:57 +02:00
redo PUSHED ;
2007-03-14 04:19:25 +01:00
}
}
}
2007-09-10 17:52:57 +02:00
fatal_error "Internal error in determine_zones()" unless scalar @ zones == scalar @ z ;
2007-03-14 04:19:25 +01:00
}
2007-05-08 00:12:42 +02:00
#
# Return true of we have any ipsec zones
#
2007-05-08 00:20:00 +02:00
sub haveipseczones () {
2007-05-08 00:12:42 +02:00
for my $ zoneref ( values % zones ) {
2007-09-12 17:03:47 +02:00
return 1 if $ zoneref - > { type } eq 'ipsec4' ;
2007-05-08 00:12:42 +02:00
}
0 ;
}
2007-03-14 04:27:29 +01:00
#
# Report about zones.
#
2007-04-08 16:42:26 +02:00
sub zone_report ()
2007-03-14 04:27:29 +01:00
{
2007-07-11 01:09:33 +02:00
progress_message2 "Determining Hosts in Zones..." ;
2007-03-14 04:27:29 +01:00
for my $ zone ( @ zones )
{
my $ zoneref = $ zones { $ zone } ;
my $ hostref = $ zoneref - > { hosts } ;
my $ type = $ zoneref - > { type } ;
my $ optionref = $ zoneref - > { options } ;
2007-09-12 17:03:47 +02:00
progress_message " $zone ($type)" ;
2007-03-14 04:27:29 +01:00
my $ printed = 0 ;
2007-03-27 01:17:46 +02:00
2007-03-14 04:27:29 +01:00
if ( $ hostref ) {
for my $ type ( sort keys %$ hostref ) {
my $ interfaceref = $ hostref - > { $ type } ;
2007-03-27 01:17:46 +02:00
2007-03-14 04:27:29 +01:00
for my $ interface ( sort keys %$ interfaceref ) {
my $ arrayref = $ interfaceref - > { $ interface } ;
for my $ groupref ( @$ arrayref ) {
my $ hosts = $ groupref - > { hosts } ;
if ( $ hosts ) {
my $ grouplist = join ',' , ( @$ hosts ) ;
progress_message " $interface:$grouplist" ;
$ printed = 1 ;
}
}
}
}
}
2007-03-27 01:17:46 +02:00
2007-06-20 00:39:41 +02:00
unless ( $ printed ) {
2007-09-12 17:03:47 +02:00
fatal_error "No bridge has been associated with zone $zone" if $ type eq 'bport4' && ! $ zoneref - > { bridge } ;
warning_message "*** $zone is an EMPTY ZONE ***" unless $ type eq 'firewall' ;
2007-06-20 00:39:41 +02:00
}
2007-03-14 04:27:29 +01:00
}
}
2007-04-08 16:42:26 +02:00
sub dump_zone_contents ()
2007-03-21 21:35:40 +01:00
{
for my $ zone ( @ zones )
{
my $ zoneref = $ zones { $ zone } ;
my $ hostref = $ zoneref - > { hosts } ;
my $ type = $ zoneref - > { type } ;
my $ optionref = $ zoneref - > { options } ;
my $ exclusions = $ zoneref - > { exclusions } ;
2007-09-12 17:03:47 +02:00
my $ entry = "$zone $type" ;
2007-03-21 21:35:40 +01:00
2007-09-12 17:03:47 +02:00
$ entry . = ":$zoneref->{bridge}" if $ type eq 'bport4' ;
2007-06-20 00:39:41 +02:00
2007-03-21 21:35:40 +01:00
if ( $ hostref ) {
for my $ type ( sort keys %$ hostref ) {
my $ interfaceref = $ hostref - > { $ type } ;
2007-03-27 01:17:46 +02:00
2007-03-21 21:35:40 +01:00
for my $ interface ( sort keys %$ interfaceref ) {
my $ arrayref = $ interfaceref - > { $ interface } ;
for my $ groupref ( @$ arrayref ) {
my $ hosts = $ groupref - > { hosts } ;
if ( $ hosts ) {
my $ grouplist = join ',' , ( @$ hosts ) ;
$ entry . = " $interface:$grouplist" ;
}
}
}
}
}
if ( @$ exclusions ) {
$ entry . = ' exclude' ;
2007-03-27 01:17:46 +02:00
2007-03-21 21:35:40 +01:00
for my $ host ( @$ exclusions ) {
$ entry . = " $host" ;
}
2007-03-27 01:17:46 +02:00
}
2007-03-21 21:35:40 +01:00
emit_unindented $ entry ;
}
}
2007-06-07 04:21:54 +02:00
#
# If the passed zone is associated with a single interface, the name of the interface is returned. Otherwise, the funtion returns '';
#
sub single_interface ( $ ) {
my $ zone = $ _ [ 0 ] ;
my $ zoneref = $ zones { $ zone } ;
2007-08-09 17:16:08 +02:00
2007-06-07 04:21:54 +02:00
fatal_error "Internal Error in single_zone()" unless $ zoneref ;
2007-08-09 17:16:08 +02:00
my @ keys = keys ( % { $ zoneref - > { interfaces } } ) ;
@ keys == 1 ? $ keys [ 0 ] : '' ;
2007-07-26 20:36:18 +02:00
}
2007-06-07 04:21:54 +02:00
2007-07-17 00:07:50 +02:00
sub add_group_to_zone ($$$$$)
{
my ( $ zone , $ type , $ interface , $ networks , $ options ) = @ _ ;
my $ typeref ;
my $ interfaceref ;
my $ arrayref ;
my $ zoneref = $ zones { $ zone } ;
my $ zonetype = $ zoneref - > { type } ;
2007-09-11 19:29:41 +02:00
my $ ifacezone = $ interfaces { $ interface } { zone } ;
2007-07-17 00:07:50 +02:00
$ zoneref - > { interfaces } { $ interface } = 1 ;
my @ newnetworks ;
my @ exclusions ;
my $ new = \ @ newnetworks ;
my $ switched = 0 ;
$ ifacezone = '' unless defined $ ifacezone ;
for my $ host ( @$ networks ) {
fatal_error "Invalid Host List" unless defined $ host and $ host ne '' ;
if ( substr ( $ host , 0 , 1 ) eq '!' ) {
fatal_error "Only one exclusion allowed in a host list" if $ switched ;
$ switched = 1 ;
$ host = substr ( $ host , 1 ) ;
$ new = \ @ exclusions ;
}
unless ( $ switched ) {
2007-09-12 17:03:47 +02:00
if ( $ type eq $ zonetype ) {
2007-07-17 00:07:50 +02:00
fatal_error "Duplicate Host Group ($interface:$host) in zone $zone" if $ ifacezone eq $ zone ;
$ ifacezone = $ zone if $ host eq ALLIPv4 ;
}
}
if ( substr ( $ host , 0 , 1 ) eq '+' ) {
fatal_error "Invalid ipset name ($host)" unless $ host =~ /^\+[a-zA-Z]\w*$/ ;
} else {
validate_host $ host ;
}
push @$ new , $ switched ? "$interface:$host" : $ host ;
}
$ zoneref - > { options } { in_out } { routeback } = 1 if $ options - > { routeback } ;
$ typeref = ( $ zoneref - > { hosts } || ( $ zoneref - > { hosts } = { } ) ) ;
$ interfaceref = ( $ typeref - > { $ type } || ( $ interfaceref = $ typeref - > { $ type } = { } ) ) ;
$ arrayref = ( $ interfaceref - > { $ interface } || ( $ interfaceref - > { $ interface } = [] ) ) ;
$ zoneref - > { options } { complex } = 1 if @$ arrayref || ( @ newnetworks > 1 ) || ( @ exclusions ) ;
push @ { $ zoneref - > { exclusions } } , @ exclusions ;
2007-07-26 20:36:18 +02:00
2007-07-17 00:07:50 +02:00
push @ { $ arrayref } , { options = > $ options ,
hosts = > \ @ newnetworks ,
2007-09-12 17:03:47 +02:00
ipsec = > $ type eq 'ipsec4' ? 'ipsec' : 'none' } ;
2007-07-17 00:07:50 +02:00
}
2007-09-10 17:52:57 +02:00
#
# Verify that the passed zone name represents a declared zone. Return a
# reference to its zone table entry.
#
sub find_zone ( $ ) {
my $ zone = $ _ [ 0 ] ;
my $ zoneref = $ zones { $ zone } ;
fatal_error "Unknown zone" unless $ zoneref ;
$ zoneref ;
}
sub zone_type ( $ ) {
find_zone ( $ _ [ 0 ] ) - > { type } ;
}
sub defined_zone ( $ ) {
$ zones { $ _ [ 0 ] } ;
}
sub all_zones () {
@ zones ;
}
sub non_firewall_zones () {
2007-09-12 17:03:47 +02:00
grep ( $ zones { $ _ } { type } ne 'firewall' , @ zones ) ;
2007-09-10 17:52:57 +02:00
}
sub complex_zones () {
grep ( $ zones { $ _ } { options } { complex } , @ zones ) ;
}
sub firewall_zone () {
$ firewall_zone ;
}
2007-07-17 00:07:50 +02:00
#
# Return a list of networks routed out of the passed interface
#
sub get_routed_networks ( $$ ) {
my ( $ interface , $ error_message ) = @ _ ;
my @ networks ;
if ( open IP , '-|' , "/sbin/ip route show dev $interface 2> /dev/null" ) {
while ( my $ route = <IP> ) {
$ route =~ s/^\s+// ;
my $ network = ( split /\s+/ , $ route ) [ 0 ] ;
if ( $ network eq 'default' ) {
fatal_error $ error_message if $ error_message ;
warning_message "default route ignored on interface $interface" ;
} else {
my ( $ address , $ vlsm ) = split '/' , $ network ;
$ vlsm = 32 unless defined $ vlsm ;
push @ networks , "$address/$vlsm" ;
}
}
close IP
}
@ networks ;
}
#
# Parse the interfaces file.
#
sub validate_interfaces_file ( $ )
{
my $ export = shift ;
use constant { SIMPLE_IF_OPTION = > 1 ,
BINARY_IF_OPTION = > 2 ,
2007-07-26 20:36:18 +02:00
ENUM_IF_OPTION = > 3 ,
2007-08-26 17:12:04 +02:00
NUMERIC_IF_OPTION = > 4 ,
MASK_IF_OPTION = > 7 ,
2007-07-26 20:36:18 +02:00
2007-08-26 17:12:04 +02:00
IF_OPTION_ZONEONLY = > 8 } ;
2007-07-17 00:07:50 +02:00
my % validoptions = ( arp_filter = > BINARY_IF_OPTION ,
arp_ignore = > ENUM_IF_OPTION ,
blacklist = > SIMPLE_IF_OPTION ,
bridge = > SIMPLE_IF_OPTION ,
detectnets = > SIMPLE_IF_OPTION ,
dhcp = > SIMPLE_IF_OPTION ,
maclist = > SIMPLE_IF_OPTION ,
logmartians = > BINARY_IF_OPTION ,
norfc1918 = > SIMPLE_IF_OPTION ,
nosmurfs = > SIMPLE_IF_OPTION ,
optional = > SIMPLE_IF_OPTION ,
proxyarp = > BINARY_IF_OPTION ,
routeback = > SIMPLE_IF_OPTION + IF_OPTION_ZONEONLY ,
routefilter = > BINARY_IF_OPTION ,
sourceroute = > BINARY_IF_OPTION ,
tcpflags = > SIMPLE_IF_OPTION ,
upnp = > SIMPLE_IF_OPTION ,
2007-08-26 17:12:04 +02:00
mss = > NUMERIC_IF_OPTION ,
2007-07-17 00:07:50 +02:00
) ;
my $ fn = open_file 'interfaces' ;
my $ first_entry = 1 ;
my @ ifaces ;
while ( read_a_line ) {
if ( $ first_entry ) {
progress_message2 "$doing $fn..." ;
$ first_entry = 0 ;
}
2007-07-26 20:36:18 +02:00
2007-07-17 00:07:50 +02:00
my ( $ zone , $ interface , $ networks , $ options ) = split_line 2 , 4 , 'interfaces file' ;
my $ zoneref ;
my $ bridge = '' ;
if ( $ zone eq '-' ) {
$ zone = '' ;
} else {
$ zoneref = $ zones { $ zone } ;
fatal_error "Unknown zone ($zone)" unless $ zoneref ;
2007-09-12 17:03:47 +02:00
fatal_error "Firewall zone not allowed in ZONE column of interface record" if $ zoneref - > { type } eq 'firewall' ;
2007-07-17 00:07:50 +02:00
}
$ networks = '' if $ networks eq '-' ;
$ options = '' if $ options eq '-' ;
( $ interface , my ( $ port , $ extra ) ) = split /:/ , $ interface , 3 ;
2007-07-26 20:36:18 +02:00
fatal_error "Invalid INTERFACE" if defined $ extra || ! $ interface ;
2007-07-17 00:07:50 +02:00
fatal_error "Invalid Interface Name ($interface)" if $ interface eq '+' ;
if ( defined $ port ) {
require_capability ( 'PHYSDEV_MATCH' , 'Bridge Ports' , '' ) ;
require_capability ( 'KLUDGEFREE' , 'Bridge Ports' , '' ) ;
fatal_error "Duplicate Interface ($port)" if $ interfaces { $ port } ;
fatal_error "$interface is not a defined bridge" unless $ interfaces { $ interface } && $ interfaces { $ interface } { options } { bridge } ;
2007-09-12 17:03:47 +02:00
fatal_error "Bridge Ports may only be associated with 'bport' zones" if $ zone && $ zoneref - > { type } ne 'bport4' ;
2007-07-17 00:07:50 +02:00
if ( $ zone ) {
if ( $ zoneref - > { bridge } ) {
fatal_error "Bridge Port zones may only be associated with a single bridge" if $ zoneref - > { bridge } ne $ interface ;
} else {
$ zoneref - > { bridge } = $ interface ;
}
}
fatal_error "Bridge Ports may not have options" if $ options && $ options ne '-' ;
next if $ port eq '' ;
fatal_error "Invalid Interface Name ($interface:$port)" unless $ port =~ /^[\w.@%-]+\+?$/ ;
$ interfaces { $ port } { bridge } = $ bridge = $ interface ;
$ interface = $ port ;
} else {
fatal_error "Duplicate Interface ($interface)" if $ interfaces { $ interface } ;
2007-09-12 17:03:47 +02:00
fatal_error "Zones of type 'bport' may only be associated with bridge ports" if $ zone && $ zoneref - > { type } eq 'bport4' ;
2007-07-17 00:07:50 +02:00
$ interfaces { $ interface } { bridge } = $ interface ;
}
2007-07-26 20:36:18 +02:00
2007-07-17 00:07:50 +02:00
my $ wildcard = 0 ;
if ( $ interface =~ /\+$/ ) {
$ wildcard = 1 ;
$ interfaces { $ interface } { root } = substr ( $ interface , 0 , - 1 ) ;
2007-07-26 20:36:18 +02:00
} else {
2007-07-17 00:07:50 +02:00
$ interfaces { $ interface } { root } = $ interface ;
}
unless ( $ networks eq '' || $ networks eq 'detect' ) {
2007-08-10 19:37:02 +02:00
my @ broadcasts = split /,/ , $ networks ;
2007-07-17 00:07:50 +02:00
2007-08-10 19:37:02 +02:00
for my $ address ( @ broadcasts ) {
2007-07-17 00:07:50 +02:00
fatal_error 'Invalid BROADCAST address' unless $ address =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ ;
}
2007-08-10 19:37:02 +02:00
if ( $ capabilities { ADDRTYPE } ) {
warning_message 'Shorewall no longer uses broadcast addresses in rule generation when Address Type Match is available' ;
} else {
$ interfaces { $ interface } { broadcasts } = \ @ broadcasts ;
}
2007-07-17 00:07:50 +02:00
}
my $ optionsref = { } ;
my % options ;
2007-07-26 20:36:18 +02:00
2007-07-17 00:07:50 +02:00
if ( $ options ) {
for my $ option ( split ',' , $ options ) {
next if $ option eq '-' ;
( $ option , my $ value ) = split /=/ , $ option ;
fatal_error "Invalid Interface option ($option)" unless my $ type = $ validoptions { $ option } ;
fatal_error "The \"$option\" option may not be specified on a multi-zone interface" if $ type & IF_OPTION_ZONEONLY && ! $ zone ;
2007-07-26 20:36:18 +02:00
2007-07-17 00:07:50 +02:00
$ type & = MASK_IF_OPTION ;
if ( $ type == SIMPLE_IF_OPTION ) {
fatal_error "Option $option does not take a value" if defined $ value ;
$ options { $ option } = 1 ;
} elsif ( $ type == BINARY_IF_OPTION ) {
$ value = 1 unless defined $ value ;
fatal_error "Option value for $option must be 0 or 1" unless ( $ value eq '0' || $ value eq '1' ) ;
fatal_error "The $option option may not be used with a wild-card interface name" if $ wildcard ;
$ options { $ option } = $ value ;
} elsif ( $ type == ENUM_IF_OPTION ) {
fatal_error "The $option option may not be used with a wild-card interface name" if $ wildcard ;
if ( $ option eq 'arp_ignore' ) {
if ( defined $ value ) {
if ( $ value =~ /^[1-3,8]$/ ) {
$ options { arp_ignore } = $ value ;
} else {
fatal_error "Invalid value ($value) for arp_ignore" ;
2007-07-26 20:36:18 +02:00
}
2007-07-17 00:07:50 +02:00
} else {
$ options { arp_ignore } = 1 ;
}
} else {
fatal_error "Internal Error in validate_interfaces_file" ;
}
2007-08-26 17:12:04 +02:00
} elsif ( $ type == NUMERIC_IF_OPTION ) {
fatal_error "The $option option requires a value" unless defined $ value ;
$ options { $ option } = numeric_value $ value ;
2007-07-17 00:07:50 +02:00
}
}
$ zoneref - > { options } { in_out } { routeback } = 1 if $ zoneref && $ options { routeback } ;
if ( $ options { bridge } ) {
require_capability ( 'PHYSDEV_MATCH' , 'The "bridge" option' , 's' ) ;
fatal_error "Bridges may not have wildcard names" if $ wildcard ;
}
} elsif ( $ port ) {
$ options { port } = 1 ;
}
2007-07-26 20:36:18 +02:00
2007-07-17 00:07:50 +02:00
$ interfaces { $ interface } { options } = $ optionsref = \ % options ;
push @ ifaces , $ interface ;
my @ networks ;
if ( $ options { detectnets } ) {
2007-09-21 18:55:28 +02:00
warning_message "Support for the 'detectnets' option will be removed from Shorewall-perl in version 4.0.5; better to use 'routefilter' and 'logmartians'" ;
2007-07-17 00:07:50 +02:00
fatal_error "The 'detectnets' option is not allowed on a multi-zone interface" unless $ zone ;
fatal_error "The 'detectnets' option may not be used with a wild-card interface name" if $ wildcard ;
fatal_error "The 'detectnets' option may not be used with the '-e' compiler option" if $ export ;
@ networks = get_routed_networks ( $ interface , 'detectnets not allowed on interface with default route' ) ;
fatal_error "No routes found through 'detectnets' interface $interface" unless @ networks || $ options { optional } ;
delete $ options { maclist } unless @ networks ;
} else {
2007-09-10 17:52:57 +02:00
@ networks = allipv4 ;
2007-07-17 00:07:50 +02:00
}
2007-09-11 19:29:41 +02:00
add_group_to_zone ( $ zone , $ zoneref - > { type } , $ interface , \ @ networks , $ optionsref ) if $ zone && @ networks ;
2007-07-17 00:07:50 +02:00
2007-09-11 19:29:41 +02:00
$ interfaces { $ interface } { zone } = $ zone ; #Must follow the call to add_group_to_zone()
2007-07-26 20:36:18 +02:00
2007-07-17 00:07:50 +02:00
progress_message " Interface \"$currentline\" Validated" ;
}
#
# We now assemble the @interfaces array such that bridge ports immediately precede their associated bridge
#
for my $ interface ( @ ifaces ) {
my $ interfaceref = $ interfaces { $ interface } ;
2007-07-26 20:36:18 +02:00
2007-07-17 00:07:50 +02:00
if ( $ interfaceref - > { options } { bridge } ) {
my @ ports = grep $ interfaces { $ _ } { options } { port } && $ interfaces { $ _ } { bridge } eq $ interface , @ ifaces ;
if ( @ ports ) {
push @ interfaces , @ ports ;
} else {
$ interfaceref - > { options } { routeback } = 1 ; #so the bridge will work properly
}
}
push @ interfaces , $ interface unless $ interfaceref - > { options } { port } ;
2007-07-26 20:36:18 +02:00
}
2007-07-17 00:07:50 +02:00
}
#
# Returns true if passed interface matches an entry in /etc/shorewall/interfaces
#
# If the passed name matches a wildcard, a entry for the name is added in %interfaces to speed up validation of other references to that name.
#
sub known_interface ($)
{
my $ interface = $ _ [ 0 ] ;
return 1 if $ interfaces { $ interface } ;
for my $ i ( @ interfaces ) {
my $ interfaceref = $ interfaces { $ i } ;
my $ val = $ interfaceref - > { root } ;
next if $ val eq $ i ;
if ( substr ( $ interface , 0 , length $ val ) eq $ val ) {
#
# Cache this result for future reference
#
$ interfaces { $ interface } = { options = > $ interfaceref - > { options } , bridge = > $ interfaceref - > { bridge } } ;
return 1 ;
}
}
0 ;
}
2007-09-10 17:52:57 +02:00
#
# Return the interfaces list
#
sub all_interfaces () {
@ interfaces ;
}
#
# Return a reference to the interfaces table entry for an interface
#
sub find_interface ( $ ) {
my $ interface = $ _ [ 0 ] ;
my $ interfaceref = $ interfaces { $ interface } ;
fatal_error "Unknown Interface ($interface)" unless $ interfaceref ;
$ interfaceref ;
}
#
2007-09-21 18:55:28 +02:00
# Returns true if there are bridge port zones defined in the config
2007-09-10 17:52:57 +02:00
#
sub have_bridges () {
2007-09-21 18:55:28 +02:00
@ bport_zones > 0 ;
2007-09-10 17:52:57 +02:00
}
2007-07-17 00:07:50 +02:00
#
# Return the bridge associated with the passed interface. If the interface is not a bridge port,
# return ''
#
sub port_to_bridge ( $ ) {
my $ portref = $ interfaces { $ _ [ 0 ] } ;
return $ portref && $ portref - > { options } { port } ? $ portref - > { bridge } : '' ;
}
#
# Return the bridge associated with the passed interface.
#
sub source_port_to_bridge ( $ ) {
my $ portref = $ interfaces { $ _ [ 0 ] } ;
return $ portref ? $ portref - > { bridge } : '' ;
}
#
# Return the 'optional' setting of the passed interface
#
sub interface_is_optional ($) {
my $ optionsref = $ interfaces { $ _ [ 0 ] } { options } ;
$ optionsref && $ optionsref - > { optional } ;
}
#
# Returns reference to array of interfaces with the passed option
#
sub find_interfaces_by_option ( $ ) {
my $ option = $ _ [ 0 ] ;
my @ ints = ( ) ;
for my $ interface ( @ interfaces ) {
my $ optionsref = $ interfaces { $ interface } { options } ;
if ( $ optionsref && defined $ optionsref - > { $ option } ) {
push @ ints , $ interface
}
}
\ @ ints ;
}
#
# Return the value of an option for an interface
#
sub get_interface_option ( $$ ) {
my ( $ interface , $ option ) = @ _ ;
$ interfaces { $ interface } { options } { $ option } ;
}
#
# Validates the hosts file. Generates entries in %zone{..}{hosts}
#
sub validate_hosts_file ()
{
my % validoptions = (
blacklist = > 1 ,
maclist = > 1 ,
norfc1918 = > 1 ,
nosmurfs = > 1 ,
routeback = > 1 ,
routefilter = > 1 ,
tcpflags = > 1 ,
) ;
my $ ipsec = 0 ;
my $ first_entry = 1 ;
my $ fn = open_file 'hosts' ;
while ( read_a_line ) {
if ( $ first_entry ) {
progress_message2 "$doing $fn..." ;
$ first_entry = 0 ;
}
my ( $ zone , $ hosts , $ options ) = split_line 2 , 3 , 'hosts file' ;
my $ zoneref = $ zones { $ zone } ;
my $ type = $ zoneref - > { type } ;
fatal_error "Unknown ZONE ($zone)" unless $ type ;
2007-09-12 17:03:47 +02:00
fatal_error 'Firewall zone not allowed in ZONE column of hosts record' if $ type eq 'firewall' ;
2007-07-17 00:07:50 +02:00
my $ interface ;
if ( $ hosts =~ /^([\w.@%-]+\+?):(.*)$/ ) {
$ interface = $ 1 ;
$ hosts = $ 2 ;
$ zoneref - > { options } { complex } = 1 if $ hosts =~ /^\+/ ;
fatal_error "Unknown interface ($interface)" unless $ interfaces { $ interface } { root } ;
} else {
fatal_error "Invalid HOST(S) column contents: $hosts" ;
}
2007-09-12 17:03:47 +02:00
if ( $ type eq 'bport4' ) {
2007-07-17 00:07:50 +02:00
if ( $ zoneref - > { bridge } eq '' ) {
fatal_error 'Bridge Port Zones may only be associated with bridge ports' unless $ interfaces { $ interface } { options } { port } ;
$ zoneref - > { bridge } = $ interfaces { $ interface } { bridge } ;
} elsif ( $ zoneref - > { bridge } ne $ interfaces { $ interface } { bridge } ) {
fatal_error "Interface $interface is not a port on bridge $zoneref->{bridge}" ;
}
}
my $ optionsref = { } ;
if ( $ options ne '-' ) {
my @ options = split ',' , $ options ;
my % options ;
for my $ option ( @ options )
{
if ( $ option eq 'ipsec' ) {
2007-09-12 17:03:47 +02:00
$ type = 'ipsec4' ;
2007-07-17 00:07:50 +02:00
$ zoneref - > { options } { complex } = 1 ;
$ ipsec = 1 ;
} elsif ( $ validoptions { $ option } ) {
$ options { $ option } = 1 ;
} else {
fatal_error "Invalid option ($option)" ;
}
}
$ optionsref = \ % options ;
}
#
# Looking for the '!' at the beginning of a list element is more straight-foward than looking for it in the middle.
#
# Be sure we don't have a ',!' in the original
#
fatal_error "Invalid hosts list" if $ hosts =~ /,!/ ;
#
# Now add a comma before '!'. Do it globally - add_group_to_zone() correctly checks for multiple exclusions
#
2007-07-26 20:36:18 +02:00
$ hosts =~ s/!/,!/g ;
2007-07-17 00:07:50 +02:00
#
# Take care of case where the hosts list begins with '!'
#
$ hosts = join ( '' , ALLIPv4 , $ hosts ) if substr ( $ hosts , 0 , 2 ) eq ',!' ;
add_group_to_zone ( $ zone , $ type , $ interface , [ split ( ',' , $ hosts ) ] , $ optionsref ) ;
progress_message " Host \"$currentline\" validated" ;
}
$ capabilities { POLICY_MATCH } = '' unless $ ipsec || haveipseczones ;
}
#
# Returns a reference to a array of host entries. Each entry is a
# reference to an array containing ( interface , polciy match type {ipsec|none} , network );
#
sub find_hosts_by_option ( $ ) {
my $ option = $ _ [ 0 ] ;
my @ hosts ;
2007-09-12 17:03:47 +02:00
for my $ zone ( grep $ zones { $ _ } { type } ne 'firewall' , @ zones ) {
2007-07-17 00:07:50 +02:00
while ( my ( $ type , $ interfaceref ) = each % { $ zones { $ zone } { hosts } } ) {
while ( my ( $ interface , $ arrayref ) = ( each % { $ interfaceref } ) ) {
for my $ host ( @ { $ arrayref } ) {
if ( $ host - > { options } { $ option } ) {
for my $ net ( @ { $ host - > { hosts } } ) {
push @ hosts , [ $ interface , $ host - > { ipsec } , $ net ] ;
}
}
}
}
}
}
for my $ interface ( @ interfaces ) {
if ( ! $ interfaces { $ interface } { zone } && $ interfaces { $ interface } { options } { $ option } ) {
push @ hosts , [ $ interface , 'none' , ALLIPv4 ] ;
}
}
\ @ hosts ;
}
2007-03-14 04:19:25 +01:00
1 ;