2
###############################################################################
4
# IPFire.org - A linux based firewall #
5
# Copyright (C) 2021 Alexander Marx <amarx@ipfire.org> #
7
# This program is free software: you can redistribute it and/or modify #
8
# it under the terms of the GNU General Public License as published by #
9
# the Free Software Foundation, either version 3 of the License, or #
10
# (at your option) any later version. #
12
# This program is distributed in the hope that it will be useful, #
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15
# GNU General Public License for more details. #
17
# You should have received a copy of the GNU General Public License #
18
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
20
###############################################################################
23
use experimental 'smartmatch';
25
no warnings 'uninitialized';
32
my %customlocationgrp=();
34
my %customservicegrp=();
43
require '/var/ipfire/general-functions.pl';
44
require '/var/ipfire/location-functions.pl';
46
my $confignet = "${General::swroot}/fwhosts/customnetworks";
47
my $confighost = "${General::swroot}/fwhosts/customhosts";
48
my $configgrp = "${General::swroot}/fwhosts/customgroups";
49
my $configlocationgrp = "${General::swroot}/fwhosts/customlocationgrp";
50
my $configsrv = "${General::swroot}/fwhosts/customservices";
51
my $configsrvgrp = "${General::swroot}/fwhosts/customservicegrp";
52
my $configccdnet = "${General::swroot}/ovpn/ccd.conf";
53
my $configccdhost = "${General::swroot}/ovpn/ovpnconfig";
54
my $configipsec = "${General::swroot}/vpn/config";
55
my $configovpn = "${General::swroot}/ovpn/settings";
58
my $netsettings = "${General::swroot}/ethernet/settings";
60
&General::readhash("/var/ipfire/ethernet/settings", \%netsettings);
61
&General::readhash("${General::swroot}/ovpn/settings", \%ovpnsettings);
62
&General::readhash("${General::swroot}/vpn/settings", \%ipsecsettings);
64
&General::readhasharray("$confignet", \%customnetwork);
65
&General::readhasharray("$confighost", \%customhost);
66
&General::readhasharray("$configgrp", \%customgrp);
67
&General::readhasharray("$configlocationgrp", \%customlocationgrp);
68
&General::readhasharray("$configccdnet", \%ccdnet);
69
&General::readhasharray("$configccdhost", \%ccdhost);
70
&General::readhasharray("$configipsec", \%ipsecconf);
71
&General::readhasharray("$configsrv", \%customservice);
72
&General::readhasharray("$configsrvgrp", \%customservicegrp);
73
&General::get_aliases(\%aliases);
75
# Get all available locations.
76
my @available_locations = &get_locations();
81
foreach my $key (sort {$a <=> $b} keys %customservice){
82
if($customservice{$key}[0] eq $val){
83
if ($customservice{$key}[0] eq $val){
84
return $customservice{$key}[2];
96
foreach my $key (sort {$a <=> $b} keys %customservicegrp){
97
if($customservicegrp{$key}[0] eq $val){
98
if (&get_srv_prot($customservicegrp{$key}[2]) eq 'TCP'){
100
}elsif(&get_srv_prot($customservicegrp{$key}[2]) eq 'UDP'){
102
}elsif(&get_srv_prot($customservicegrp{$key}[2]) eq 'ICMP'){
105
#Protocols used in servicegroups
106
push (@ips,$customservicegrp{$key}[2]);
110
if ($tcp eq '1'){push (@ips,'TCP');}
111
if ($udp eq '1'){push (@ips,'UDP');}
112
if ($icmp eq '1'){push (@ips,'ICMP');}
113
my $back=join(",",@ips);
122
foreach my $key (sort {$a <=> $b} keys %customservice){
123
if($customservice{$key}[0] eq $val && $customservice{$key}[2] eq $prot){
124
return $customservice{$key}[$field];
135
foreach my $key (sort {$a <=> $b} keys %customservicegrp){
136
if($customservicegrp{$key}[0] eq $val){
137
if ($prot ne 'ICMP'){
138
$value=&get_srv_port($customservicegrp{$key}[2],1,$prot);
139
}elsif ($prot eq 'ICMP'){
140
$value=&get_srv_port($customservicegrp{$key}[2],3,$prot);
142
push (@ips,$value) if ($value ne '') ;
146
if ($#ips gt 0){$back="-m multiport --dports ";}else{$back="--dport ";}
147
}elsif ($prot eq 'ICMP'){
148
$back="--icmp-type ";
151
$back.=join(",",@ips);
158
foreach my $key (sort {$a <=> $b} keys %ipsecconf){
159
#adapt $val to reflect real name without subnet (if rule with only one ipsec subnet is created)
160
my @tmpval = split (/\|/, $val);
162
if($ipsecconf{$key}[1] eq $val){
163
return $ipsecconf{$key}[$field];
167
sub get_ipsec_host_ip
171
foreach my $key (sort {$a <=> $b} keys %ipsecconf){
172
if($ipsecconf{$key}[1] eq $val){
173
return $ipsecconf{$key}[$field];
180
foreach my $key (keys %ipsecconf) {
181
if ($ipsecconf{$key}[1] eq $val) {
190
foreach my $key (sort {$a <=> $b} keys %ccdhost){
191
if($ccdhost{$key}[1] eq $val){
192
return $ccdhost{$key}[$field];
200
foreach my $key (sort {$a <=> $b} keys %ccdhost){
201
if($ccdhost{$key}[1] eq $val){
202
return $ccdhost{$key}[$field];
211
foreach my $key (sort {$a <=> $b} keys %ccdnet){
212
if($ccdnet{$key}[0] eq $val){
213
return $ccdnet{$key}[$field];
221
foreach my $key (sort {$a <=> $b} keys %customgrp){
222
if ($customgrp{$key}[0] eq $val){
223
&get_address($customgrp{$key}[3],$src);
233
return "0.0.0.0/0.0.0.0";
234
}elsif($val eq 'GREEN'){
235
return "$netsettings{'GREEN_NETADDRESS'}/$netsettings{'GREEN_NETMASK'}";
236
}elsif($val eq 'ORANGE'){
237
return "$netsettings{'ORANGE_NETADDRESS'}/$netsettings{'ORANGE_NETMASK'}";
238
}elsif($val eq 'BLUE'){
239
return "$netsettings{'BLUE_NETADDRESS'}/$netsettings{'BLUE_NETMASK'}";
240
}elsif($val eq 'RED'){
242
}elsif($val =~ /OpenVPN/i){
243
return "$ovpnsettings{'DOVPN_SUBNET'}";
244
}elsif($val =~ /IPsec/i){
245
return "$ipsecsettings{'RW_NET'}";
246
}elsif($val eq 'IPFire'){
253
if($net eq "$netsettings{'GREEN_NETADDRESS'}/$netsettings{'GREEN_NETMASK'}"){
254
return "$netsettings{'GREEN_DEV'}";
256
if($net eq "$netsettings{'ORANGE_NETADDRESS'}/$netsettings{'ORANGE_NETMASK'}"){
257
return "$netsettings{'ORANGE_DEV'}";
259
if($net eq "$netsettings{'BLUE_NETADDRESS'}/$netsettings{'BLUE_NETMASK'}"){
260
return "$netsettings{'BLUE_DEV'}";
262
if($net eq "0.0.0.0/0") {
263
return &get_external_interface();
270
foreach my $key (sort {$a <=> $b} keys %customnetwork){
271
if($customnetwork{$key}[0] eq $val){
272
return "$customnetwork{$key}[1]/$customnetwork{$key}[2]";
280
foreach my $key (sort {$a <=> $b} keys %customhost){
281
if($customhost{$key}[0] eq $val){
282
if ($customhost{$key}[1] eq 'mac' && $src eq 'src'){
283
return "-m mac --mac-source $customhost{$key}[2]";
284
}elsif($customhost{$key}[1] eq 'ip' && $src eq 'src'){
285
return "$customhost{$key}[2]";
286
}elsif($customhost{$key}[1] eq 'ip' && $src eq 'tgt'){
287
return "$customhost{$key}[2]";
288
}elsif($customhost{$key}[1] eq 'mac' && $src eq 'tgt'){
305
if ($type eq "src") {
306
$addr_type = $$hash{$key}[3];
307
$value = $$hash{$key}[4];
309
} elsif ($type eq "tgt") {
310
$addr_type = $$hash{$key}[5];
311
$value = $$hash{$key}[6];
314
if ($addr_type ~~ ["cust_grp_src", "cust_grp_tgt"]) {
315
foreach my $grp (sort {$a <=> $b} keys %customgrp) {
316
if ($customgrp{$grp}[0] eq $value) {
317
my @address = &get_address($customgrp{$grp}[3], $customgrp{$grp}[2], $type);
318
next if ($address[0][0] eq 'none');
320
push(@addresses, @address);
324
}elsif ($addr_type ~~ ["cust_location_src", "cust_location_tgt"] && $value =~ "group:") {
325
$value=substr($value,6);
326
foreach my $grp (sort {$a <=> $b} keys %customlocationgrp) {
327
if ($customlocationgrp{$grp}[0] eq $value) {
328
my @address = &get_address($addr_type, $customlocationgrp{$grp}[2], $type);
331
push(@addresses, @address);
336
my @address = &get_address($addr_type, $value, $type);
339
push(@addresses, @address);
353
# If the user manually typed an address, we just check if it is a MAC
354
# address. Otherwise, we assume that it is an IP address.
355
if ($key ~~ ["src_addr", "tgt_addr"]) {
356
if (&General::validmac($value)) {
357
push(@ret, ["-m mac --mac-source $value", ""]);
359
push(@ret, [$value, ""]);
362
# If a default network interface (GREEN, BLUE, etc.) is selected, we
363
# try to get the corresponding address of the network.
364
} elsif ($key ~~ ["std_net_src", "std_net_tgt", "Standard Network"]) {
365
my $external_interface = &get_external_interface();
367
my $network_address = &get_std_net_ip($value, $external_interface);
369
if ($network_address) {
370
my $interface = &get_interface($network_address);
371
push(@ret, [$network_address, $interface]);
375
} elsif ($key ~~ ["cust_net_src", "cust_net_tgt", "Custom Network"]) {
376
my $network_address = &get_net_ip($value);
377
if ($network_address) {
378
push(@ret, [$network_address, ""]);
382
} elsif ($key ~~ ["cust_host_src", "cust_host_tgt", "Custom Host"]) {
383
my $host_address = &get_host_ip($value, $type);
385
push(@ret, [$host_address, ""]);
389
} elsif ($key ~~ ["ovpn_net_src", "ovpn_net_tgt", "OpenVPN static network"]) {
390
my $network_address = &get_ovpn_net_ip($value, 1);
391
if ($network_address) {
392
push(@ret, [$network_address, ""]);
396
} elsif ($key ~~ ["ovpn_host_src", "ovpn_host_tgt", "OpenVPN static host"]) {
397
my $host_address = &get_ovpn_host_ip($value, 33);
399
push(@ret, [$host_address, ""]);
403
} elsif ($key ~~ ["ovpn_n2n_src", "ovpn_n2n_tgt", "OpenVPN N-2-N"]) {
404
my $network_address = &get_ovpn_n2n_ip($value, 11);
405
if ($network_address) {
406
push(@ret, [$network_address, ""]);
410
} elsif ($key ~~ ["ipsec_net_src", "ipsec_net_tgt", "IpSec Network"]) {
411
#Check if we have multiple subnets and only want one of them
412
if ( $value =~ /\|/ ){
413
my @parts = split(/\|/, $value);
414
push(@ret, [$parts[1], ""]);
416
my $interface_mode = &get_ipsec_net_ip($value, 36);
417
if ($interface_mode ~~ ["gre", "vti"]) {
418
my $id = &get_ipsec_id($value);
419
push(@ret, ["0.0.0.0/0", "${interface_mode}${id}"]);
421
my $network_address = &get_ipsec_net_ip($value, 11);
422
my @nets = split(/\|/, $network_address);
423
foreach my $net (@nets) {
424
push(@ret, [$net, ""]);
429
# The firewall's own IP addresses.
430
} elsif ($key ~~ ["ipfire", "ipfire_src"]) {
432
if ($value eq "ALL") {
433
push(@ret, ["0/0", ""]);
436
} elsif ($value eq "GREEN") {
437
push(@ret, [$netsettings{"GREEN_ADDRESS"}, ""]);
440
} elsif ($value eq "BLUE") {
441
push(@ret, [$netsettings{"BLUE_ADDRESS"}, ""]);
444
} elsif ($value eq "ORANGE") {
445
push(@ret, [$netsettings{"ORANGE_ADDRESS"}, ""]);
448
} elsif ($value ~~ ["RED", "RED1"]) {
449
my $address = &get_external_address();
451
push(@ret, [$address, ""]);
456
my $alias = &get_alias($value);
458
push(@ret, [$alias, ""]);
462
# Handle rule options with a location as source.
463
} elsif ($key eq "cust_location_src") {
464
# Check if the given location is available.
465
if(&location_is_available($value)) {
466
# Get external interface.
467
my $external_interface = &get_external_interface();
469
push(@ret, ["-m set --match-set $value src", "$external_interface"]);
472
# Handle rule options with a location as target.
473
} elsif ($key eq "cust_location_tgt") {
474
# Check if the given location is available.
475
if(&location_is_available($value)) {
476
# Get external interface.
477
my $external_interface = &get_external_interface();
479
push(@ret, ["-m set --match-set $value dst", "$external_interface"]);
482
# If nothing was selected, we assume "any".
484
push(@ret, ["0/0", ""]);
489
sub get_external_interface()
491
open(IFACE, "/var/ipfire/red/iface") or return "";
497
sub get_external_address()
499
open(ADDR, "/var/ipfire/red/local-ipaddress") or return "";
500
my $address = <ADDR>;
509
foreach my $alias (sort keys %aliases) {
511
return $aliases{$alias}{"IPT"};
516
sub get_nat_address {
520
# Any static address of any zone.
521
if ($zone eq "AUTO") {
522
if ($source && ($source !~ m/mac/i )) {
523
my $firewall_ip = &get_internal_firewall_ip_address($source, 1);
528
$firewall_ip = &get_matching_firewall_address($source, 1);
534
return &get_external_address();
536
} elsif ($zone eq "RED" || $zone eq "GREEN" || $zone eq "ORANGE" || $zone eq "BLUE") {
537
return $netsettings{$zone . "_ADDRESS"};
539
} elsif ($zone ~~ ["Default IP", "ALL"]) {
540
return &get_external_address();
543
my $alias = &get_alias($zone);
545
$alias = &get_external_address();
550
print_error("Could not find NAT address");
553
sub get_internal_firewall_ip_addresses
555
my $use_orange = shift;
557
my @zones = ("GREEN", "BLUE");
559
push(@zones, "ORANGE");
563
for my $zone (@zones) {
564
next unless (exists $netsettings{$zone . "_ADDRESS"});
566
my $zone_address = $netsettings{$zone . "_ADDRESS"};
567
push(@addresses, $zone_address);
572
sub get_matching_firewall_address
575
my $use_orange = shift;
577
my ($address, $netmask) = split("/", $addr);
579
my @zones = ("GREEN", "BLUE");
581
push(@zones, "ORANGE");
584
foreach my $zone (@zones) {
585
next unless (exists $netsettings{$zone . "_ADDRESS"});
587
my $zone_subnet = $netsettings{$zone . "_NETADDRESS"};
588
my $zone_mask = $netsettings{$zone . "_NETMASK"};
590
if (&General::IpInSubnet($address, $zone_subnet, $zone_mask)) {
591
return $netsettings{$zone . "_ADDRESS"};
597
sub get_internal_firewall_ip_address
600
my $use_orange = shift;
602
my ($net_address, $net_mask) = split("/", $subnet);
603
if ((!$net_mask) || ($net_mask ~~ ["32", "255.255.255.255"])) {
607
# Convert net mask into correct format for &General::IpInSubnet().
608
$net_mask = &General::iporsubtodec($net_mask);
610
my @addresses = &get_internal_firewall_ip_addresses($use_orange);
611
foreach my $zone_address (@addresses) {
612
if (&General::IpInSubnet($zone_address, $net_address, $net_mask)) {
613
return $zone_address;
620
sub get_locations() {
621
return &Location::Functions::get_locations();
624
# Function to check if a database of a given location is
626
sub location_is_available($) {
627
my ($requested_location) = @_;
629
# Loop through the global array of available locations.
630
foreach my $location (@available_locations) {
631
# Check if the current processed location is the searched one.
632
if($location eq $requested_location) {
633
# If it is part of the array, return "1" - True.
638
# If we got here, the given location is not part of the array of available
639
# zones. Return nothing.