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.